epi-sim 0.2.2.0 → 0.4.2
raw patch · 24 files changed
+2100/−2031 lines, 24 filesdep −cassavadep −trifectadep ~aesondep ~basedep ~bytestringPVP ok
version bump matches the API change (PVP)
Dependencies removed: cassava, trifecta
Dependency ranges changed: aeson, base, bytestring, hspec, mwc-random, primitive, statistics, vector
API changes (from Hackage documentation)
- Epidemic: STBirth :: EpidemicEvent -> (SampleTree, SampleTree) -> SampleTree
- Epidemic: STDeath :: EpidemicEvent -> SampleTree
- Epidemic: TTBirth :: Person -> EpidemicEvent -> (TransmissionTree, TransmissionTree) -> TransmissionTree
- Epidemic: TTDeath :: People -> EpidemicEvent -> TransmissionTree
- Epidemic: TTUnresolved :: Person -> TransmissionTree
- Epidemic: birthProb :: ModelParameters a => a -> Time -> Maybe Probability
- Epidemic: class ModelParameters a
- Epidemic: class Population a
- Epidemic: data SampleTree
- Epidemic: data TransmissionTree
- Epidemic: eventRate :: ModelParameters a => a -> Time -> Maybe Rate
- Epidemic: hasDescendentWhich :: [EpidemicEvent] -> (Person -> Bool) -> Person -> Bool
- Epidemic: hasSampledDescendent :: [EpidemicEvent] -> Person -> Bool
- Epidemic: hasSampledLeaf :: TransmissionTree -> Bool
- Epidemic: infectiousPeople :: Population a => a -> Maybe People
- Epidemic: instance GHC.Show.Show Epidemic.SampleTree
- Epidemic: instance GHC.Show.Show Epidemic.TransmissionTree
- Epidemic: isInfected :: Population a => a -> Bool
- Epidemic: rNaught :: ModelParameters a => a -> Time -> Maybe Double
- Epidemic: removedPeople :: Population a => a -> Maybe People
- Epidemic: sampleTree :: TransmissionTree -> SampleTree
- Epidemic: sampleTreeEvents :: SampleTree -> [EpidemicEvent]
- Epidemic: sampleTreeEvents' :: SampleTree -> [EpidemicEvent]
- Epidemic: samplingEvent :: [EpidemicEvent] -> Person -> EpidemicEvent
- Epidemic: susceptiblePeople :: Population a => a -> Maybe People
- Epidemic: transmissionTree :: [EpidemicEvent] -> Person -> TransmissionTree
- Epidemic: wasSampled :: [EpidemicEvent] -> Person -> Bool
- Epidemic.BDSCOD: allEvents :: BDSCODParameters -> Time -> (Time, [EpidemicEvent], BDSCODPopulation, Integer) -> GenIO -> IO (Time, [EpidemicEvent], BDSCODPopulation, Integer)
- Epidemic.BDSCOD: configuration :: Time -> (Rate, Rate, Rate, [(Time, Probability)], Rate, [(Time, Probability)]) -> Maybe (SimulationConfiguration BDSCODParameters BDSCODPopulation)
- Epidemic.BDSCOD: instance Epidemic.ModelParameters Epidemic.BDSCOD.BDSCODParameters
- Epidemic.BDSCOD: instance Epidemic.Population Epidemic.BDSCOD.BDSCODPopulation
- Epidemic.BDSCOD: instance GHC.Show.Show Epidemic.BDSCOD.BDSCODPopulation
- Epidemic.BDSCOD: observedEvents :: [EpidemicEvent] -> Maybe [EpidemicEvent]
- Epidemic.BirthDeath: allEvents :: BDRates -> Time -> (Time, [EpidemicEvent], BDPopulation, Integer) -> GenIO -> IO (Time, [EpidemicEvent], BDPopulation, Integer)
- Epidemic.BirthDeath: configuration :: Time -> (Rate, Rate) -> Maybe (SimulationConfiguration BDRates BDPopulation)
- Epidemic.BirthDeath: instance Epidemic.ModelParameters Epidemic.BirthDeath.BDRates
- Epidemic.BirthDeath: instance Epidemic.Population Epidemic.BirthDeath.BDPopulation
- Epidemic.BirthDeath: instance GHC.Show.Show Epidemic.BirthDeath.BDPopulation
- Epidemic.BirthDeathSampling: allEvents :: BDSRates -> Time -> (Time, [EpidemicEvent], BDSPopulation, Integer) -> GenIO -> IO (Time, [EpidemicEvent], BDSPopulation, Integer)
- Epidemic.BirthDeathSampling: configuration :: Time -> (Rate, Rate, Rate) -> SimulationConfiguration BDSRates BDSPopulation
- Epidemic.BirthDeathSampling: instance Epidemic.ModelParameters Epidemic.BirthDeathSampling.BDSRates
- Epidemic.BirthDeathSampling: instance Epidemic.Population Epidemic.BirthDeathSampling.BDSPopulation
- Epidemic.BirthDeathSampling: instance GHC.Show.Show Epidemic.BirthDeathSampling.BDSPopulation
- Epidemic.BirthDeathSamplingCatastropheOccurrence: allEvents :: BDSCOParameters -> Time -> (Time, [EpidemicEvent], BDSCOPopulation, Integer) -> GenIO -> IO (Time, [EpidemicEvent], BDSCOPopulation, Integer)
- Epidemic.BirthDeathSamplingCatastropheOccurrence: configuration :: Time -> (Rate, Rate, Rate, [(Time, Probability)], Rate) -> Maybe (SimulationConfiguration BDSCOParameters BDSCOPopulation)
- Epidemic.BirthDeathSamplingCatastropheOccurrence: instance Epidemic.ModelParameters Epidemic.BirthDeathSamplingCatastropheOccurrence.BDSCOParameters
- Epidemic.BirthDeathSamplingCatastropheOccurrence: instance Epidemic.Population Epidemic.BirthDeathSamplingCatastropheOccurrence.BDSCOPopulation
- Epidemic.BirthDeathSamplingCatastropheOccurrence: instance GHC.Show.Show Epidemic.BirthDeathSamplingCatastropheOccurrence.BDSCOPopulation
- Epidemic.BirthDeathSamplingCatastropheOccurrence: observedEvents :: [EpidemicEvent] -> [EpidemicEvent]
- Epidemic.BirthDeathSamplingOccurrence: allEvents :: BDSORates -> Time -> (Time, [EpidemicEvent], BDSOPopulation, Integer) -> GenIO -> IO (Time, [EpidemicEvent], BDSOPopulation, Integer)
- Epidemic.BirthDeathSamplingOccurrence: configuration :: Time -> (Rate, Rate, Rate, Rate) -> SimulationConfiguration BDSORates BDSOPopulation
- Epidemic.BirthDeathSamplingOccurrence: instance Epidemic.ModelParameters Epidemic.BirthDeathSamplingOccurrence.BDSORates
- Epidemic.BirthDeathSamplingOccurrence: instance Epidemic.Population Epidemic.BirthDeathSamplingOccurrence.BDSOPopulation
- Epidemic.BirthDeathSamplingOccurrence: instance GHC.Show.Show Epidemic.BirthDeathSamplingOccurrence.BDSOPopulation
- Epidemic.BirthDeathSamplingOccurrence: observedEvents :: [EpidemicEvent] -> [EpidemicEvent]
- Epidemic.InhomogeneousBD: allEvents :: InhomBDRates -> Time -> (Time, [EpidemicEvent], InhomBDPop, Integer) -> GenIO -> IO (Time, [EpidemicEvent], InhomBDPop, Integer)
- Epidemic.InhomogeneousBD: configuration :: Time -> ([(Time, Rate)], Rate) -> Maybe (SimulationConfiguration InhomBDRates InhomBDPop)
- Epidemic.InhomogeneousBD: instance Epidemic.ModelParameters Epidemic.InhomogeneousBD.InhomBDRates
- Epidemic.InhomogeneousBD: instance Epidemic.Population Epidemic.InhomogeneousBD.InhomBDPop
- Epidemic.InhomogeneousBD: instance GHC.Show.Show Epidemic.InhomogeneousBD.InhomBDPop
- Epidemic.InhomogeneousBDS: allEvents :: InhomBDSRates -> Time -> (Time, [EpidemicEvent], InhomBDSPop, Integer) -> GenIO -> IO (Time, [EpidemicEvent], InhomBDSPop, Integer)
- Epidemic.InhomogeneousBDS: configuration :: Time -> ([(Time, Rate)], Rate, Rate) -> Maybe (SimulationConfiguration InhomBDSRates InhomBDSPop)
- Epidemic.InhomogeneousBDS: inhomBDSRates :: [(Time, Rate)] -> Rate -> Rate -> Maybe InhomBDSRates
- Epidemic.InhomogeneousBDS: instance Epidemic.ModelParameters Epidemic.InhomogeneousBDS.InhomBDSRates
- Epidemic.InhomogeneousBDS: instance Epidemic.Population Epidemic.InhomogeneousBDS.InhomBDSPop
- Epidemic.InhomogeneousBDS: instance GHC.Show.Show Epidemic.InhomogeneousBDS.InhomBDSPop
- Epidemic.InhomogeneousBDS: observedEvents :: [EpidemicEvent] -> [EpidemicEvent]
- Epidemic.Types.Events: Catastrophe :: Time -> People -> EpidemicEvent
- Epidemic.Types.Events: Disaster :: Time -> People -> EpidemicEvent
- Epidemic.Types.Events: Occurrence :: Time -> Person -> EpidemicEvent
- Epidemic.Types.Events: PointProcessEvents :: [EpidemicEvent] -> PointProcessEvents
- Epidemic.Types.Events: RBranch :: EpidemicEvent -> ReconstructedTree -> ReconstructedTree -> ReconstructedTree
- Epidemic.Types.Events: RLeaf :: EpidemicEvent -> ReconstructedTree
- Epidemic.Types.Events: Sampling :: Time -> Person -> EpidemicEvent
- Epidemic.Types.Events: asNewickString :: Newick t => (Time, Person) -> t -> Maybe (Builder, [EpidemicEvent])
- Epidemic.Types.Events: class Newick t
- Epidemic.Types.Events: data ReconstructedTree
- Epidemic.Types.Events: instance Data.Csv.Conversion.FromRecord Epidemic.Types.Events.EpidemicEvent
- Epidemic.Types.Events: instance Data.Csv.Conversion.ToRecord Epidemic.Types.Events.EpidemicEvent
- Epidemic.Types.Events: instance Epidemic.Types.Events.Newick Epidemic.Types.Events.EpidemicTree
- Epidemic.Types.Events: instance Epidemic.Types.Events.Newick Epidemic.Types.Events.ReconstructedTree
- Epidemic.Types.Events: instance GHC.Classes.Eq Epidemic.Types.Events.ReconstructedTree
- Epidemic.Types.Events: instance GHC.Show.Show Epidemic.Types.Events.ReconstructedTree
- Epidemic.Types.Events: maybeReconstructedTree :: EpidemicTree -> Maybe ReconstructedTree
- Epidemic.Types.Events: newtype PointProcessEvents
- Epidemic.Types.Events: pointProcessEvents :: EpidemicTree -> PointProcessEvents
- Epidemic.Types.Parameter: Timed :: [(Time, a)] -> Timed a
- Epidemic.Types.Parameter: asTimed :: Num a => [(Time, a)] -> Maybe (Timed a)
- Epidemic.Types.Parameter: cadlagValue :: Timed a -> Time -> Maybe a
- Epidemic.Types.Parameter: cadlagValue' :: [(Time, a)] -> Time -> Maybe a
- Epidemic.Types.Parameter: diracDeltaValue :: Timed a -> Time -> Maybe a
- Epidemic.Types.Parameter: diracDeltaValue' :: [(Time, a)] -> Time -> Maybe a
- Epidemic.Types.Parameter: hasTime :: Timed a -> Time -> Bool
- Epidemic.Types.Parameter: hasTime' :: [(Time, a)] -> Time -> Bool
- Epidemic.Types.Parameter: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Epidemic.Types.Parameter.Timed a)
- Epidemic.Types.Parameter: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Epidemic.Types.Parameter.Timed a)
- Epidemic.Types.Parameter: instance GHC.Base.Semigroup (Epidemic.Types.Parameter.Timed a)
- Epidemic.Types.Parameter: instance GHC.Classes.Eq a => GHC.Classes.Eq (Epidemic.Types.Parameter.Timed a)
- Epidemic.Types.Parameter: instance GHC.Generics.Generic (Epidemic.Types.Parameter.Timed a)
- Epidemic.Types.Parameter: instance GHC.Show.Show a => GHC.Show.Show (Epidemic.Types.Parameter.Timed a)
- Epidemic.Types.Parameter: isAscending :: Ord a => [a] -> Bool
- Epidemic.Types.Parameter: newtype Timed a
- Epidemic.Types.Parameter: nextTime :: Timed a -> Time -> Maybe Time
- Epidemic.Types.Parameter: nextTime' :: [(Time, a)] -> Time -> Maybe Time
- Epidemic.Types.Parameter: type Time = Double
- Epidemic.Types.Population: instance Data.Csv.Conversion.FromField Epidemic.Types.Population.People
- Epidemic.Types.Population: instance Data.Csv.Conversion.FromField Epidemic.Types.Population.Person
- Epidemic.Types.Population: instance Data.Csv.Conversion.ToField Epidemic.Types.Population.People
- Epidemic.Types.Population: instance Data.Csv.Conversion.ToField Epidemic.Types.Population.Person
- Epidemic.Utility: SimulationConfiguration :: r -> p -> Integer -> Time -> SimulationConfiguration r p
- Epidemic.Utility: [newIdentifier] :: SimulationConfiguration r p -> Integer
- Epidemic.Utility: [population] :: SimulationConfiguration r p -> p
- Epidemic.Utility: [rates] :: SimulationConfiguration r p -> r
- Epidemic.Utility: [timeLimit] :: SimulationConfiguration r p -> Time
- Epidemic.Utility: data SimulationConfiguration r p
- Epidemic.Utility: isNonReconTreeObservation :: EpidemicEvent -> Bool
- Epidemic.Utility: newickBranch :: (TokenParsing f, Monad f, CharParsing f) => f NBranch
- Epidemic.Utility: newickBranchSet :: (TokenParsing f, Monad f, CharParsing f) => f NBranchSet
- Epidemic.Utility: newickInternal :: (TokenParsing f, Monad f, CharParsing f) => f NSubtree
- Epidemic.Utility: newickLeaf :: (Monad f, CharParsing f) => f NSubtree
- Epidemic.Utility: newickLength :: (TokenParsing f, Monad f, CharParsing f) => f NLength
- Epidemic.Utility: newickName :: (Monad f, CharParsing f) => f NName
- Epidemic.Utility: newickSubtree :: (TokenParsing f, Monad f, CharParsing f) => f NSubtree
- Epidemic.Utility: newickTree :: (TokenParsing f, Monad f, CharParsing f) => f NTree
+ Epidemic: allEvents :: (ModelParameters a b, Population b) => SimulationRandEvent a b -> a -> AbsoluteTime -> Maybe (b -> Bool) -> SimulationState b -> GenIO -> IO (SimulationState b)
+ Epidemic.Model.BDSCOD: BDSCODParameters :: Rate -> Rate -> Rate -> Timed Probability -> Rate -> Timed Probability -> BDSCODParameters
+ Epidemic.Model.BDSCOD: BDSCODPopulation :: People -> BDSCODPopulation
+ Epidemic.Model.BDSCOD: configuration :: TimeDelta -> Bool -> (Rate, Rate, Rate, [(AbsoluteTime, Probability)], Rate, [(AbsoluteTime, Probability)]) -> Maybe (SimulationConfiguration BDSCODParameters BDSCODPopulation)
+ Epidemic.Model.BDSCOD: data BDSCODParameters
+ Epidemic.Model.BDSCOD: data BDSCODPopulation
+ Epidemic.Model.BDSCOD: instance Epidemic.Types.Parameter.ModelParameters Epidemic.Model.BDSCOD.BDSCODParameters Epidemic.Model.BDSCOD.BDSCODPopulation
+ Epidemic.Model.BDSCOD: instance Epidemic.Types.Population.Population Epidemic.Model.BDSCOD.BDSCODPopulation
+ Epidemic.Model.BDSCOD: instance GHC.Show.Show Epidemic.Model.BDSCOD.BDSCODPopulation
+ Epidemic.Model.BDSCOD: randomEvent :: SimulationRandEvent BDSCODParameters BDSCODPopulation
+ Epidemic.Model.InhomogeneousBDS: InhomBDSPop :: People -> InhomBDSPop
+ Epidemic.Model.InhomogeneousBDS: InhomBDSRates :: Timed Rate -> Rate -> Rate -> InhomBDSRates
+ Epidemic.Model.InhomogeneousBDS: configuration :: TimeDelta -> Bool -> ([(AbsoluteTime, Rate)], Rate, Rate) -> Maybe (SimulationConfiguration InhomBDSRates InhomBDSPop)
+ Epidemic.Model.InhomogeneousBDS: data InhomBDSPop
+ Epidemic.Model.InhomogeneousBDS: data InhomBDSRates
+ Epidemic.Model.InhomogeneousBDS: inhomBDSRates :: Timed Rate -> Rate -> Rate -> Maybe InhomBDSRates
+ Epidemic.Model.InhomogeneousBDS: instance Epidemic.Types.Parameter.ModelParameters Epidemic.Model.InhomogeneousBDS.InhomBDSRates Epidemic.Model.InhomogeneousBDS.InhomBDSPop
+ Epidemic.Model.InhomogeneousBDS: instance Epidemic.Types.Population.Population Epidemic.Model.InhomogeneousBDS.InhomBDSPop
+ Epidemic.Model.InhomogeneousBDS: instance GHC.Show.Show Epidemic.Model.InhomogeneousBDS.InhomBDSPop
+ Epidemic.Model.InhomogeneousBDS: randomEvent :: SimulationRandEvent InhomBDSRates InhomBDSPop
+ Epidemic.Model.LogisticBDSD: LogisticBDSDParameters :: Rate -> Int -> Rate -> Rate -> Timed Probability -> LogisticBDSDParameters
+ Epidemic.Model.LogisticBDSD: LogisticBDSDPopulation :: People -> LogisticBDSDPopulation
+ Epidemic.Model.LogisticBDSD: [paramsBirthRate] :: LogisticBDSDParameters -> Rate
+ Epidemic.Model.LogisticBDSD: [paramsCapacity] :: LogisticBDSDParameters -> Int
+ Epidemic.Model.LogisticBDSD: [paramsDeathRate] :: LogisticBDSDParameters -> Rate
+ Epidemic.Model.LogisticBDSD: [paramsDisasters] :: LogisticBDSDParameters -> Timed Probability
+ Epidemic.Model.LogisticBDSD: [paramsSamplingRate] :: LogisticBDSDParameters -> Rate
+ Epidemic.Model.LogisticBDSD: configuration :: TimeDelta -> Bool -> (Rate, Int, Rate, Rate, [(AbsoluteTime, Probability)]) -> Either String (SimulationConfiguration LogisticBDSDParameters LogisticBDSDPopulation)
+ Epidemic.Model.LogisticBDSD: data LogisticBDSDParameters
+ Epidemic.Model.LogisticBDSD: instance Epidemic.Types.Parameter.ModelParameters Epidemic.Model.LogisticBDSD.LogisticBDSDParameters Epidemic.Model.LogisticBDSD.LogisticBDSDPopulation
+ Epidemic.Model.LogisticBDSD: instance Epidemic.Types.Population.Population Epidemic.Model.LogisticBDSD.LogisticBDSDPopulation
+ Epidemic.Model.LogisticBDSD: instance GHC.Show.Show Epidemic.Model.LogisticBDSD.LogisticBDSDParameters
+ Epidemic.Model.LogisticBDSD: instance GHC.Show.Show Epidemic.Model.LogisticBDSD.LogisticBDSDPopulation
+ Epidemic.Model.LogisticBDSD: newtype LogisticBDSDPopulation
+ Epidemic.Model.LogisticBDSD: randomEvent :: SimulationRandEvent LogisticBDSDParameters LogisticBDSDPopulation
+ Epidemic.Types.Events: Extinction :: EpidemicEvent
+ Epidemic.Types.Events: IndividualSample :: AbsoluteTime -> Person -> Bool -> EpidemicEvent
+ Epidemic.Types.Events: PopulationSample :: AbsoluteTime -> People -> Bool -> EpidemicEvent
+ Epidemic.Types.Events: StoppingTime :: EpidemicEvent
+ Epidemic.Types.Events: indSampPerson :: EpidemicEvent -> Person
+ Epidemic.Types.Events: indSampSeq :: EpidemicEvent -> Bool
+ Epidemic.Types.Events: indSampTime :: EpidemicEvent -> AbsoluteTime
+ Epidemic.Types.Events: isExtinctionOrStopping :: EpidemicEvent -> Bool
+ Epidemic.Types.Events: popSampPeople :: EpidemicEvent -> People
+ Epidemic.Types.Events: popSampSeq :: EpidemicEvent -> Bool
+ Epidemic.Types.Events: popSampTime :: EpidemicEvent -> AbsoluteTime
+ Epidemic.Types.Newick: ampersandBuilder :: Builder
+ Epidemic.Types.Newick: asNewickString :: Newick t => (AbsoluteTime, Person) -> t -> Maybe (Builder, [EpidemicEvent])
+ Epidemic.Types.Newick: catastrophePeopleBuilder :: People -> Builder
+ Epidemic.Types.Newick: class Newick t
+ Epidemic.Types.Newick: colonBuilder :: Builder
+ Epidemic.Types.Newick: commaBuilder :: Builder
+ Epidemic.Types.Newick: instance Epidemic.Types.Newick.Newick Epidemic.Types.Observations.ReconstructedTree
+ Epidemic.Types.Newick: leftBraceBuilder :: Builder
+ Epidemic.Types.Newick: rightBraceBuilder :: Builder
+ Epidemic.Types.Observations: Observation :: EpidemicEvent -> Observation
+ Epidemic.Types.Observations: PointProcessEvents :: [Observation] -> PointProcessEvents
+ Epidemic.Types.Observations: RBranch :: Observation -> ReconstructedTree -> ReconstructedTree -> ReconstructedTree
+ Epidemic.Types.Observations: RLeaf :: Observation -> ReconstructedTree
+ Epidemic.Types.Observations: data ReconstructedTree
+ Epidemic.Types.Observations: instance Data.Aeson.Types.FromJSON.FromJSON Epidemic.Types.Observations.Observation
+ Epidemic.Types.Observations: instance Data.Aeson.Types.ToJSON.ToJSON Epidemic.Types.Observations.Observation
+ Epidemic.Types.Observations: instance GHC.Classes.Eq Epidemic.Types.Observations.Observation
+ Epidemic.Types.Observations: instance GHC.Classes.Eq Epidemic.Types.Observations.ReconstructedTree
+ Epidemic.Types.Observations: instance GHC.Classes.Ord Epidemic.Types.Observations.Observation
+ Epidemic.Types.Observations: instance GHC.Generics.Generic Epidemic.Types.Observations.Observation
+ Epidemic.Types.Observations: instance GHC.Show.Show Epidemic.Types.Observations.Observation
+ Epidemic.Types.Observations: instance GHC.Show.Show Epidemic.Types.Observations.ReconstructedTree
+ Epidemic.Types.Observations: maybeReconstructedTree :: EpidemicTree -> Either String ReconstructedTree
+ Epidemic.Types.Observations: newtype Observation
+ Epidemic.Types.Observations: newtype PointProcessEvents
+ Epidemic.Types.Observations: observedEvents :: [EpidemicEvent] -> Either String [Observation]
+ Epidemic.Types.Observations: pointProcessEvents :: EpidemicTree -> PointProcessEvents
+ Epidemic.Types.Observations: reconstructedTreeEvents :: ReconstructedTree -> [Observation]
+ Epidemic.Types.Parameter: birthProb :: ModelParameters a p => p -> a -> AbsoluteTime -> Maybe Probability
+ Epidemic.Types.Parameter: class (Population p) => ModelParameters a p
+ Epidemic.Types.Parameter: eventRate :: ModelParameters a p => p -> a -> AbsoluteTime -> Maybe Rate
+ Epidemic.Types.Parameter: rNaught :: ModelParameters a p => p -> a -> AbsoluteTime -> Maybe Double
+ Epidemic.Types.Population: Identifier :: Integer -> Identifier
+ Epidemic.Types.Population: class Population a
+ Epidemic.Types.Population: infectiousPeople :: Population a => a -> Maybe People
+ Epidemic.Types.Population: instance Data.Aeson.Types.FromJSON.FromJSON Epidemic.Types.Population.Identifier
+ Epidemic.Types.Population: instance Data.Aeson.Types.ToJSON.ToJSON Epidemic.Types.Population.Identifier
+ Epidemic.Types.Population: instance GHC.Classes.Eq Epidemic.Types.Population.Identifier
+ Epidemic.Types.Population: instance GHC.Generics.Generic Epidemic.Types.Population.Identifier
+ Epidemic.Types.Population: instance GHC.Show.Show Epidemic.Types.Population.Identifier
+ Epidemic.Types.Population: isInfected :: Population a => a -> Bool
+ Epidemic.Types.Population: newtype Identifier
+ Epidemic.Types.Population: removedPeople :: Population a => a -> Maybe People
+ Epidemic.Types.Population: susceptiblePeople :: Population a => a -> Maybe People
+ Epidemic.Types.Simulation: SimulationConfiguration :: r -> p -> Identifier -> AbsoluteTime -> TimeDelta -> Maybe (p -> Bool) -> Bool -> SimulationConfiguration r p
+ Epidemic.Types.Simulation: SimulationState :: (AbsoluteTime, [EpidemicEvent], b, Identifier) -> SimulationState b
+ Epidemic.Types.Simulation: TerminatedSimulation :: SimulationState b
+ Epidemic.Types.Simulation: [SimulationRandEvent] :: (ModelParameters a b, Population b) => (a -> AbsoluteTime -> b -> Identifier -> GenIO -> IO (AbsoluteTime, EpidemicEvent, b, Identifier)) -> SimulationRandEvent a b
+ Epidemic.Types.Simulation: [scNewIdentifier] :: SimulationConfiguration r p -> Identifier
+ Epidemic.Types.Simulation: [scPopulation] :: SimulationConfiguration r p -> p
+ Epidemic.Types.Simulation: [scRates] :: SimulationConfiguration r p -> r
+ Epidemic.Types.Simulation: [scRequireCherry] :: SimulationConfiguration r p -> Bool
+ Epidemic.Types.Simulation: [scSimDuration] :: SimulationConfiguration r p -> TimeDelta
+ Epidemic.Types.Simulation: [scStartTime] :: SimulationConfiguration r p -> AbsoluteTime
+ Epidemic.Types.Simulation: [scValidPopulation] :: SimulationConfiguration r p -> Maybe (p -> Bool)
+ Epidemic.Types.Simulation: data SimulationConfiguration r p
+ Epidemic.Types.Simulation: data SimulationRandEvent a b
+ Epidemic.Types.Simulation: data SimulationState b
+ Epidemic.Types.Simulation: instance GHC.Classes.Eq b => GHC.Classes.Eq (Epidemic.Types.Simulation.SimulationState b)
+ Epidemic.Types.Simulation: instance GHC.Show.Show b => GHC.Show.Show (Epidemic.Types.Simulation.SimulationState b)
+ Epidemic.Types.Time: AbsoluteTime :: Double -> AbsoluteTime
+ Epidemic.Types.Time: TimeDelta :: Double -> TimeDelta
+ Epidemic.Types.Time: Timed :: [(AbsoluteTime, a)] -> Timed a
+ Epidemic.Types.Time: allTimes :: Timed a -> [AbsoluteTime]
+ Epidemic.Types.Time: asTimed :: Num a => [(AbsoluteTime, a)] -> Maybe (Timed a)
+ Epidemic.Types.Time: cadlagValue :: Timed a -> AbsoluteTime -> Maybe a
+ Epidemic.Types.Time: diracDeltaValue :: Timed a -> AbsoluteTime -> Maybe a
+ Epidemic.Types.Time: hasTime :: Timed a -> AbsoluteTime -> Bool
+ Epidemic.Types.Time: instance Data.Aeson.Types.FromJSON.FromJSON Epidemic.Types.Time.AbsoluteTime
+ Epidemic.Types.Time: instance Data.Aeson.Types.FromJSON.FromJSON Epidemic.Types.Time.TimeDelta
+ Epidemic.Types.Time: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Epidemic.Types.Time.Timed a)
+ Epidemic.Types.Time: instance Data.Aeson.Types.ToJSON.ToJSON Epidemic.Types.Time.AbsoluteTime
+ Epidemic.Types.Time: instance Data.Aeson.Types.ToJSON.ToJSON Epidemic.Types.Time.TimeDelta
+ Epidemic.Types.Time: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Epidemic.Types.Time.Timed a)
+ Epidemic.Types.Time: instance GHC.Base.Semigroup (Epidemic.Types.Time.Timed a)
+ Epidemic.Types.Time: instance GHC.Classes.Eq Epidemic.Types.Time.AbsoluteTime
+ Epidemic.Types.Time: instance GHC.Classes.Eq Epidemic.Types.Time.TimeDelta
+ Epidemic.Types.Time: instance GHC.Classes.Eq a => GHC.Classes.Eq (Epidemic.Types.Time.Timed a)
+ Epidemic.Types.Time: instance GHC.Classes.Ord Epidemic.Types.Time.AbsoluteTime
+ Epidemic.Types.Time: instance GHC.Classes.Ord Epidemic.Types.Time.TimeDelta
+ Epidemic.Types.Time: instance GHC.Generics.Generic (Epidemic.Types.Time.Timed a)
+ Epidemic.Types.Time: instance GHC.Generics.Generic Epidemic.Types.Time.AbsoluteTime
+ Epidemic.Types.Time: instance GHC.Generics.Generic Epidemic.Types.Time.TimeDelta
+ Epidemic.Types.Time: instance GHC.Show.Show Epidemic.Types.Time.AbsoluteTime
+ Epidemic.Types.Time: instance GHC.Show.Show Epidemic.Types.Time.TimeDelta
+ Epidemic.Types.Time: instance GHC.Show.Show a => GHC.Show.Show (Epidemic.Types.Time.Timed a)
+ Epidemic.Types.Time: isAscending :: Ord a => [a] -> Bool
+ Epidemic.Types.Time: newtype AbsoluteTime
+ Epidemic.Types.Time: newtype TimeDelta
+ Epidemic.Types.Time: newtype Timed a
+ Epidemic.Types.Time: nextTime :: Timed a -> AbsoluteTime -> Maybe AbsoluteTime
+ Epidemic.Types.Time: timeAfterDelta :: AbsoluteTime -> TimeDelta -> AbsoluteTime
+ Epidemic.Types.Time: timeDelta :: AbsoluteTime -> AbsoluteTime -> TimeDelta
+ Epidemic.Utility: maybeToRight :: a -> Maybe b -> Either a b
+ Epidemic.Utility: simulationWithGenIO :: (ModelParameters a b, Population b) => SimulationConfiguration a b -> (a -> AbsoluteTime -> Maybe (b -> Bool) -> SimulationState b -> GenIO -> IO (SimulationState b)) -> GenIO -> IO [EpidemicEvent]
- Epidemic: firstScheduled :: Time -> Timed Probability -> Maybe (Time, Probability)
+ Epidemic: firstScheduled :: AbsoluteTime -> Timed Probability -> Maybe (AbsoluteTime, Probability)
- Epidemic: noScheduledEvent :: Time -> Time -> Timed Probability -> Bool
+ Epidemic: noScheduledEvent :: AbsoluteTime -> AbsoluteTime -> Timed Probability -> Bool
- Epidemic.Types.Events: Infection :: Time -> Person -> Person -> EpidemicEvent
+ Epidemic.Types.Events: Infection :: AbsoluteTime -> Person -> Person -> EpidemicEvent
- Epidemic.Types.Events: Removal :: Time -> Person -> EpidemicEvent
+ Epidemic.Types.Events: Removal :: AbsoluteTime -> Person -> EpidemicEvent
- Epidemic.Types.Events: eventTime :: EpidemicEvent -> Time
+ Epidemic.Types.Events: eventTime :: EpidemicEvent -> AbsoluteTime
- Epidemic.Types.Events: maybeEpidemicTree :: [EpidemicEvent] -> Maybe EpidemicTree
+ Epidemic.Types.Events: maybeEpidemicTree :: [EpidemicEvent] -> Either String EpidemicTree
- Epidemic.Types.Population: Person :: Integer -> Person
+ Epidemic.Types.Population: Person :: Identifier -> Person
- Epidemic.Utility: inhomExponential :: PrimMonad m => Timed Double -> Gen (PrimState m) -> m Double
+ Epidemic.Utility: inhomExponential :: PrimMonad m => Timed Double -> AbsoluteTime -> Gen (PrimState m) -> m (Maybe AbsoluteTime)
- Epidemic.Utility: initialIdentifier :: Integer
+ Epidemic.Utility: initialIdentifier :: Identifier
- Epidemic.Utility: newPerson :: Integer -> (Person, Integer)
+ Epidemic.Utility: newPerson :: Identifier -> (Person, Identifier)
- Epidemic.Utility: randInhomExp :: PrimMonad m => Double -> Timed Double -> Gen (PrimState m) -> m (Maybe Double)
+ Epidemic.Utility: randInhomExp :: PrimMonad m => AbsoluteTime -> Timed Double -> Gen (PrimState m) -> m (Maybe AbsoluteTime)
- Epidemic.Utility: randomPerson :: Vector Person -> GenIO -> IO (Person, Vector Person)
+ Epidemic.Utility: randomPerson :: People -> GenIO -> IO (Person, People)
- Epidemic.Utility: simulation :: ModelParameters a => Bool -> SimulationConfiguration a b -> (a -> Time -> (Time, [EpidemicEvent], b, Integer) -> GenIO -> IO (Time, [EpidemicEvent], b, Integer)) -> IO [EpidemicEvent]
+ Epidemic.Utility: simulation :: (ModelParameters a b, Population b) => SimulationConfiguration a b -> (a -> AbsoluteTime -> Maybe (b -> Bool) -> SimulationState b -> GenIO -> IO (SimulationState b)) -> IO [EpidemicEvent]
- Epidemic.Utility: simulation' :: ModelParameters a => SimulationConfiguration a b -> (a -> Time -> (Time, [EpidemicEvent], b, Integer) -> GenIO -> IO (Time, [EpidemicEvent], b, Integer)) -> GenIO -> IO [EpidemicEvent]
+ Epidemic.Utility: simulation' :: (ModelParameters a b, Population b) => SimulationConfiguration a b -> (a -> AbsoluteTime -> Maybe (b -> Bool) -> SimulationState b -> GenIO -> IO (SimulationState b)) -> GenIO -> IO [EpidemicEvent]
- Epidemic.Utility: simulationWithSystemRandom :: ModelParameters a => Bool -> SimulationConfiguration a b -> (a -> Time -> (Time, [EpidemicEvent], b, Integer) -> GenIO -> IO (Time, [EpidemicEvent], b, Integer)) -> IO [EpidemicEvent]
+ Epidemic.Utility: simulationWithSystemRandom :: (ModelParameters a b, Population b) => SimulationConfiguration a b -> (a -> AbsoluteTime -> Maybe (b -> Bool) -> SimulationState b -> GenIO -> IO (SimulationState b)) -> IO [EpidemicEvent]
Files
- ARCHITECTURE.md +15/−0
- ChangeLog.md +116/−1
- README.md +6/−17
- epi-sim.cabal +66/−87
- src/Epidemic.hs +86/−157
- src/Epidemic/BDSCOD.hs +0/−180
- src/Epidemic/BirthDeath.hs +0/−97
- src/Epidemic/BirthDeathSampling.hs +0/−101
- src/Epidemic/BirthDeathSamplingCatastropheOccurrence.hs +0/−136
- src/Epidemic/BirthDeathSamplingOccurrence.hs +0/−119
- src/Epidemic/InhomogeneousBD.hs +0/−115
- src/Epidemic/InhomogeneousBDS.hs +0/−135
- src/Epidemic/Model/BDSCOD.hs +170/−0
- src/Epidemic/Model/InhomogeneousBDS.hs +148/−0
- src/Epidemic/Model/LogisticBDSD.hs +193/−0
- src/Epidemic/Types/Events.hs +117/−295
- src/Epidemic/Types/Newick.hs +80/−0
- src/Epidemic/Types/Observations.hs +121/−1
- src/Epidemic/Types/Parameter.hs +9/−75
- src/Epidemic/Types/Population.hs +22/−17
- src/Epidemic/Types/Simulation.hs +50/−0
- src/Epidemic/Types/Time.hs +155/−0
- src/Epidemic/Utility.hs +155/−151
- test/Spec.hs +591/−347
+ ARCHITECTURE.md view
@@ -0,0 +1,15 @@+# ARCHITECTURE++This document describes the high-level structure of the `epi-sim` package to+make it easier to navigate the source code.++## Modules++The `Epidemic` and `Epidemic.Utilty` modules provide basic functions for+simulating epidemics and working with the resulting data sets, eg simulate a+birth-death process filter out the observed cases and write them to CSV. The+submodules under `Epidemic.Types` provide combinators and functionality for+working these these data and writing your own simulations. There are some basic+simulation models already provided in the `Epidemic.Model` submodules. For the+most part, writing a new epidemic model revolves around definining the+`randomEvent` function.
ChangeLog.md view
@@ -1,5 +1,120 @@ # Changelog for epi-sim +## 0.4.2++- Include `simulationWithGenIO` and add `scRequireCherry` to the+ `SimulationConfiguration` record type to make it easier to control how+ simulations are conditioned upon particular observations.+- Documentation.+- Bug fix in edge case of no sequenced samples.++## 0.4.1++- Update the `simulationWithSystemRandom` function so that this works again.++## 0.4.0.0++- The following changes to the `EpidemicEvent` type will be the real sticking+ point in moving from `0.3.0.0` to `0.4.0.0`:+ +```+-- | Events that can occur in an epidemic with their absolute time.+data EpidemicEvent+ = Infection AbsoluteTime Person Person -- ^ infection time, infector, infectee+ | Removal AbsoluteTime Person -- ^ removal without observation+ | Sampling AbsoluteTime Person -- ^ removal and inclusion in phylogeny+ | Catastrophe AbsoluteTime People -- ^ scheduled sampling of lineages+ | Occurrence AbsoluteTime Person -- ^ removal and observed by not in phylogeny+ | Disaster AbsoluteTime People -- ^ scheduled occurrence of lineages+ | Extinction -- ^ epidemic went extinct time time can be recovered from the preceeding removal+ | StoppingTime -- ^ the simulation reached the stopping time+ deriving (Show, Generic, Eq)+```++becomes++```+-- | Events that can occur in an epidemic with their absolute time.+data EpidemicEvent+ = Infection AbsoluteTime Infector Infectee+ | Removal AbsoluteTime Person+ | IndividualSample+ { indSampTime :: AbsoluteTime+ , indSampPerson :: Person+ , indSampSeq :: Bool+ }+ | PopulationSample+ { popSampTime :: AbsoluteTime+ , popSampPeople :: People+ , popSampSeq :: Bool+ }+ | Extinction -- ^ epidemic went extinct time time can be recovered from the preceeding removal+ | StoppingTime -- ^ the simulation reached the stopping time+ deriving (Show, Generic, Eq)+```+ +- Remove CSV export, now there is only JSON export. If you want to include an+ orphan instance for working with `cassava` the following might be useful++```+instance Csv.ToRecord EpidemicEvent where+ toRecord e =+ case e of+ (Infection time person1 person2) ->+ Csv.record+ [ "infection"+ , Csv.toField time+ , Csv.toField person1+ , Csv.toField person2+ ]+ (Removal time person) ->+ Csv.record ["removal", Csv.toField time, Csv.toField person, "NA"]+ (Sampling time person) ->+ Csv.record ["sampling", Csv.toField time, Csv.toField person, "NA"]+ (Catastrophe time people) ->+ Csv.record ["catastrophe", Csv.toField time, Csv.toField people, "NA"]+ (Occurrence time person) ->+ Csv.record ["occurrence", Csv.toField time, Csv.toField person, "NA"]+ (Disaster time people) ->+ Csv.record ["disaster", Csv.toField time, Csv.toField people, "NA"]+ Extinction -> Csv.record ["extinction", "NA", "NA", "NA"]+ StoppingTime -> Csv.record ["stop", "NA", "NA", "NA"]+```++- Move the Newick material into `Epidemic.Types.Newick`.+- Remvoe the `TransmissionTree` and `SampleTree` data types because there is+ already the `EpidemicTree` and `ReconstructedTree` in the+ `Epidemic.Types.Events` which should be used preferentially.+- Remove dependency on `trifecta` since this functionality is not necessary.++## 0.3.0.0++- Add an `ARCHITECTURE.md` file to outline the structure of this package.+- Move all the temporal types into their own submodule `Epidemic.Types.Time` so+ they are easier to isolate.+- Provide a `Epidemic.Types.Observations` module to provide the functionality+ surrounding extracting the observed events from a simulation.+- Add `Extinction` and `StoppingTime` constructors for the `EpidemicEvent` type+ so that we can encode why the simulation finished in the events. As a result+ of this change, every simulation that was not terminated early should end with+ an `Extinction` event or a `StoppingTime` event.+- Update the resolver to 17.2+- Extend the `ModelParameters` class to include a `Population` parameter type+ since this is needed to compute event rates in for the logistic model.+- Create `Epidemic.Type.Simulation` module for types relating to running generic+ simulations from the models to avoid confusion as to where these are defined.+- Move class definitions into corresponding `Epidemic.Type.X` modules.+- Add a `Epidemic.Model.LogisticBDSD` module implementing a logistic birth-death+ process with unscheduled sampling and scheduled unsequenced sampling.+- Move the models into a new `Epidemic.Model` module so that it is clearer that+ these are really just examples of putting together functionality provided by+ the rest of the library.+- Use a new type `Identifier` to represent identities of people rather than a+ raw integer this way it is clearer what it really is.+- Replace `Time` with `AbsoluteTime` and `TimeDelta` types to make it explicit+ what is being represented.+- Reduce the number of models that are included to the more interesting subset.+ ## 0.2.2.0 - Clean up for a release candidate.@@ -10,7 +125,7 @@ versions to use. - Remove dependency upon `epi-types` by moving its modules into this package and include the tests from that package.-- Remove unsed `Setup.hs` file.+- Remove unused `Setup.hs` file. ## 0.2.0.1
README.md view
@@ -4,21 +4,10 @@ ## Available models -1. Birth-Death (see `Epidemic.BirthDeath`)-2. Birth-Death-Sampling (see `Epidemic.BirthDeathSampling`)-3. Birth-Death-Sampling-Occurrence (see `Epidemic.BirthDeathSamplingOccurrence`)-4. Birth-Death-Sampling-Catastrophe-Occurrence (see `Epidemic.BirthDeathSamplingCatastropheOccurrence`)-5. Birth-Death-Sampling-Catastrophe-Occurrence-Disaster (see `Epidemic.BDSCOD`)-6. Inhomogeneous Birth-Death (see `Epidemic.InhomogeneousBD`)-7. Inhomogeneous Birth-Death-Sampling (see `Epidemic.InhomogeneousBDS`)--## Output+Although this package supports the definition of new models there are some that+are implemented already in the `Epidemic.Model` module. Implemented models+include: -The output is a CSV with a header encoding which events occurred when and to-whom: `event,time,primaryPerson,secondaryPerson`. The *primary person* is either-the infecting person or the person who has been removed in some manner, the-*secondary person* is the person who was infected, or this is a missing value.-There are functions to assist in extracting observations from a full simulation:-`birthDeathSamplingOccurrenceObservedEvents`. In the case of a catastrophe event-where multiple individuals may be removed, they are represented as a colon-separated list of identifiers in the `primaryPerson` field.+1. Birth-Death-Sampling-Catastrophe-Occurrence-Disaster (see `Epidemic.Model.BDSCOD`)+2. Inhomogeneous Birth-Death-Sampling (see `Epidemic.Model.InhomogeneousBDS`)+3. Logistic Birth-Death-Sampling-Disaster (see `Epidemic.Model.LogisticBDSD`)
epi-sim.cabal view
@@ -1,106 +1,85 @@-cabal-version: 1.22-name: epi-sim-version: 0.2.2.0-synopsis: A library for simulating epidemics as birth-death processes.+cabal-version: 1.22+name: epi-sim+version: 0.4.2+synopsis:+ A library for simulating epidemics as birth-death processes.+ description: A library for simulating epidemics, with a focus on phylodynamics and observation models. .- /Available models/- .- * Birth-Death (see `Epidemic.BirthDeath`)- .- * Birth-Death-Sampling (see `Epidemic.BirthDeathSampling`)- .- * Birth-Death-Sampling-Occurrence (see `Epidemic.BirthDeathSamplingOccurrence`)- .- * Birth-Death-Sampling-Catastrophe-Occurrence (see `Epidemic.BirthDeathSamplingCatastropheOccurrence`)- .- * Birth-Death-Sampling-Catastrophe-Occurrence-Disaster (see `Epidemic.BDSCOD`)- .- * Inhomogeneous Birth-Death (see `Epidemic.InhomogeneousBD`)- .- * Inhomogeneous Birth-Death-Sampling (see `Epidemic.InhomogeneousBDS`)- .- /Output format/+ Although this package supports the definition of new models there are some that+ are implemented already in the `Epidemic.Model` module. Implemented models+ include: .- The output is a CSV with a header encoding which events occurred when and to- whom: @event,time,primaryPerson,secondaryPerson@. The @primary person@ is either- the infecting person or the person who has been removed in some manner, the- @secondary person@ is the person who was infected, or this is a missing value.- There are functions to assist in extracting observations from a full simulation:- @birthDeathSamplingOccurrenceObservedEvents@. In the case of a catastrophe event- where multiple individuals may be removed, they are represented as a colon- separated list of identifiers in the @primaryPerson@ field.+ 1. Birth-Death-Sampling-Catastrophe-Occurrence-Disaster (see `Epidemic.Model.BDSCOD`)+ 2. Inhomogeneous Birth-Death-Sampling (see `Epidemic.Model.InhomogeneousBDS`)+ 3. Logistic Birth-Death-Sampling-Disaster (see `Epidemic.Model.LogisticBDSD`) .-homepage: https://github.com/aezarebski/epi-sim#readme-bug-reports: https://github.com/aezarebski/epi-sim/issues-author: Alexander Zarebski-maintainer: aezarebski@gmail.com-copyright: 2020 Alexander Zarebski-license: MIT-license-file: LICENSE-build-type: Simple-category: Simulation++homepage: https://github.com/aezarebski/epi-sim#readme+bug-reports: https://github.com/aezarebski/epi-sim/issues+author: Alexander Zarebski+maintainer: aezarebski@gmail.com+copyright: 2020 Alexander Zarebski+license: MIT+license-file: LICENSE+build-type: Simple+category: Simulation extra-source-files:- README.md- ChangeLog.md+ ChangeLog.md+ README.md+ ARCHITECTURE.md source-repository head- type: git+ type: git location: https://github.com/aezarebski/epi-sim library exposed-modules:- Epidemic- Epidemic.BDSCOD- Epidemic.BirthDeath- Epidemic.BirthDeathSampling- Epidemic.BirthDeathSamplingCatastropheOccurrence- Epidemic.BirthDeathSamplingOccurrence- Epidemic.InhomogeneousBD- Epidemic.InhomogeneousBDS- Epidemic.Types.Events- Epidemic.Types.Observations- Epidemic.Types.Parameter- Epidemic.Types.Population- Epidemic.Utility- other-modules:- Paths_epi_sim- hs-source-dirs:- src+ Epidemic+ Epidemic.Model.BDSCOD+ Epidemic.Model.InhomogeneousBDS+ Epidemic.Model.LogisticBDSD+ Epidemic.Types.Events+ Epidemic.Types.Newick+ Epidemic.Types.Observations+ Epidemic.Types.Parameter+ Epidemic.Types.Population+ Epidemic.Types.Simulation+ Epidemic.Types.Time+ Epidemic.Utility++ other-modules: Paths_epi_sim+ hs-source-dirs: src build-depends:- aeson >= 1.4.0 && < 1.5,- base >= 4.8.2 && < 4.14,- bytestring >= 0.10.6 && < 0.11,- primitive >= 0.6.1 && < 0.8,- vector >= 0.11.0 && < 0.13,- cassava >= 0.5.2 && < 0.6,- hspec >= 2.7.4 && < 2.8,- mwc-random >= 0.14.0 && < 0.15,- statistics >= 0.15.0 && < 0.16,- trifecta >= 2.1 && < 2.2+ aeson >=1.4.7 && <1.6+ , base >=4.14.1 && <4.15+ , bytestring >=0.10.10 && <0.11+ , hspec >=2.7.6 && <2.8+ , mwc-random >=0.14.0 && <0.16+ , primitive >=0.7.0 && <0.8+ , statistics >=0.15.2 && <0.16+ , vector >=0.12.1 && <0.13+ default-language: Haskell2010- ghc-options: -Wincomplete-patterns+ ghc-options: -Wincomplete-patterns test-suite epi-sim-test- type: exitcode-stdio-1.0- main-is: Spec.hs- other-modules:- Paths_epi_sim- hs-source-dirs:- test- ghc-options: -threaded -rtsopts -with-rtsopts=-N+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules: Paths_epi_sim+ hs-source-dirs: test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends:- aeson,- base,- bytestring,- epi-sim,- primitive,- vector,- cassava,- hspec,- mwc-random,- statistics,- trifecta+ aeson+ , base+ , bytestring+ , epi-sim+ , hspec+ , mwc-random+ , primitive+ , statistics+ , vector+ default-language: Haskell2010
src/Epidemic.hs view
@@ -1,206 +1,135 @@+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE OverloadedStrings #-}+ module Epidemic where import Control.Monad import qualified Data.ByteString as B import Data.ByteString.Internal (c2w)-import Data.Csv import Data.List (nub)+import Data.Maybe (fromJust, isJust, isNothing) import qualified Data.Vector as V import Data.Word import Epidemic.Types.Events import Epidemic.Types.Parameter import Epidemic.Types.Population+import Epidemic.Types.Simulation+ ( SimulationConfiguration(..)+ , SimulationRandEvent(..)+ , SimulationState(..)+ )+import Epidemic.Types.Time (AbsoluteTime(..), Timed(..), diracDeltaValue, nextTime) import GHC.Generics (Generic)--+import System.Random.MWC -- | The number of people added or removed in an event. eventPopDelta :: EpidemicEvent -> Integer-eventPopDelta e = case e of- Infection{} -> 1- Removal _ _ -> -1- Sampling _ _ -> -1- Catastrophe _ people -> fromIntegral $ numPeople people- Occurrence _ _ -> -1- Disaster _ people -> fromIntegral $ numPeople people+eventPopDelta e =+ case e of+ Infection {} -> 1+ Removal {} -> -1+ IndividualSample {} -> -1+ PopulationSample {..} -> fromIntegral $ numPeople popSampPeople+ StoppingTime -> 0 -- | The first scheduled event after a given time.-firstScheduled :: Time -- ^ The given time- -> Timed Probability -- ^ The information about all scheduled events- -> Maybe (Time,Probability)+firstScheduled ::+ AbsoluteTime -- ^ The given time+ -> Timed Probability -- ^ The information about all scheduled events+ -> Maybe (AbsoluteTime, Probability) firstScheduled time timedProb = do time' <- nextTime timedProb time prob' <- diracDeltaValue timedProb time'- return (time',prob')+ return (time', prob') -- | Predicate for whether there is a scheduled event during an interval.-noScheduledEvent :: Time -- ^ Start time for interval- -> Time -- ^ End time for interval- -> Timed Probability -- ^ Information about all scheduled events- -> Bool+noScheduledEvent ::+ AbsoluteTime -- ^ Start time for interval+ -> AbsoluteTime -- ^ End time for interval+ -> Timed Probability -- ^ Information about all scheduled events+ -> Bool noScheduledEvent _ _ (Timed []) = True noScheduledEvent a b (Timed ((shedTime, _):scheduledEvents)) =- not (a < shedTime && shedTime <= b) && noScheduledEvent a b (Timed scheduledEvents)+ not (a < shedTime && shedTime <= b) &&+ noScheduledEvent a b (Timed scheduledEvents) +-- | A list of the people involved in an 'EpidemicEvent'. personsInEvent :: EpidemicEvent -> [Person]-personsInEvent e = case e of- (Infection _ p1 p2) -> [p1,p2]- (Removal _ p) -> [p]- (Sampling _ p) -> [p]- (Catastrophe _ (People persons)) -> V.toList persons- (Occurrence _ p) -> [p]- (Disaster _ (People persons)) -> V.toList persons+personsInEvent e =+ case e of+ Infection _ p1 p2 -> [p1, p2]+ Removal _ p -> [p]+ (IndividualSample {..}) -> [indSampPerson]+ (PopulationSample {..}) ->+ V.toList personVec+ where+ (People personVec) = popSampPeople+ Extinction -> []+ StoppingTime -> [] peopleInEvents :: [EpidemicEvent] -> People peopleInEvents events = People . V.fromList . nub . concat $ map personsInEvent events - -- | Predicate for whether the first person infected the second in the given event-infected :: Person -- ^ Potential infector- -> Person -- ^ Potential infectee- -> EpidemicEvent -- ^ Given event- -> Bool+infected ::+ Person -- ^ Potential infector+ -> Person -- ^ Potential infectee+ -> EpidemicEvent -- ^ Given event+ -> Bool infected p1 p2 e = case e of (Infection _ infector infectee) -> infector == p1 && infectee == p2 _ -> False - -- | The people infected by a particular person in a list of events.-infectedBy :: Person -- ^ Potential infector- -> [EpidemicEvent] -- ^ Events- -> People+infectedBy ::+ Person -- ^ Potential infector+ -> [EpidemicEvent] -- ^ Events+ -> People infectedBy person events = case events of [] -> People V.empty- (Infection _ infector infectee :es) ->+ (Infection _ infector infectee:es) -> if infector == person then addPerson infectee $ infectedBy person es else infectedBy person es (_:es) -> infectedBy person es ---- | Predicate for whether a person or one of their descendents satisfies a--- predicate-hasDescendentWhich :: [EpidemicEvent]- -> (Person -> Bool)- -> Person- -> Bool-hasDescendentWhich events predicate person =- predicate person ||- any (hasDescendentWhich events predicate) (V.toList descendents)- where- (People descendents) = infectedBy person events--hasSampledDescendent :: [EpidemicEvent] -> Person -> Bool-hasSampledDescendent events = hasDescendentWhich events (wasSampled events)---- | Predicate for whether a person was sampled in the given events-wasSampled :: [EpidemicEvent] -- ^ The given events- -> Person -- ^ The person of interest- -> Bool-wasSampled events person =- case events of- (Sampling _ sampledPerson:es) ->- sampledPerson == person || wasSampled es person- (Catastrophe _ (People sampledPeople):es) ->- person `V.elem` sampledPeople || wasSampled es person- (_:es) -> wasSampled es person- [] -> False---- | Return the sampling event of a person who was sampled.-samplingEvent :: [EpidemicEvent] -> Person -> EpidemicEvent-samplingEvent events person =- case events of- (se@(Sampling _ sampledPerson):remainingEvents) ->- if sampledPerson == person- then se- else samplingEvent remainingEvents person- (se@(Catastrophe _ (People sampledPeople)):remainingEvents) ->- if person `V.elem` sampledPeople- then se- else samplingEvent remainingEvents person- _:remainingEvents -> samplingEvent remainingEvents person- _ -> error "person does not appear to have been sampled."---class ModelParameters a where- rNaught :: a -> Time -> Maybe Double- eventRate :: a -> Time -> Maybe Rate- birthProb :: a -> Time -> Maybe Probability--class Population a where- susceptiblePeople :: a -> Maybe People- infectiousPeople :: a -> Maybe People- removedPeople :: a -> Maybe People- isInfected :: a -> Bool---data TransmissionTree- = TTUnresolved Person- | TTDeath People EpidemicEvent- | TTBirth Person EpidemicEvent (TransmissionTree, TransmissionTree)- deriving (Show)---- | A transmission tree of all the events starting from a given person-transmissionTree :: [EpidemicEvent] -> Person -> TransmissionTree-transmissionTree (e@(Infection _ p1 p2):es) person- | p1 == person = TTBirth person e (transmissionTree es p1,transmissionTree es p2)- | null es = TTUnresolved person- | otherwise = transmissionTree es person-transmissionTree (e@(Removal _ p1):es) person- | p1 == person = TTDeath (peopleInEvents [e]) e- | otherwise = transmissionTree es person-transmissionTree (e@(Sampling _ p1):es) person- | p1 == person = TTDeath (peopleInEvents [e]) e- | otherwise = transmissionTree es person-transmissionTree (e@(Catastrophe _ (People people)):es) person- | person `V.elem` people = TTDeath (People people) e- | otherwise = transmissionTree es person-transmissionTree (e@(Occurrence _ p1):es) person- | p1 == person = TTDeath (peopleInEvents [e]) e- | otherwise = transmissionTree es person-transmissionTree (e@(Disaster _ (People people)):es) person- | person `V.elem` people = TTDeath (People people) e- | otherwise = transmissionTree es person-transmissionTree [] person = TTUnresolved person---- | A predicate for whether there is a sampled leaf in the transmission tree-hasSampledLeaf :: TransmissionTree -> Bool-hasSampledLeaf t = case t of- (TTUnresolved _) -> False- (TTDeath _ (Sampling _ _)) -> True- (TTDeath _ (Catastrophe _ _)) -> True- (TTDeath _ _) -> False- (TTBirth _ _ (t1,t2)) -> hasSampledLeaf t1 || hasSampledLeaf t2--data SampleTree- = STBirth EpidemicEvent (SampleTree,SampleTree)- | STDeath EpidemicEvent- deriving (Show)---- | A transmission tree with all non-sampling leaves removed-sampleTree :: TransmissionTree -> SampleTree-sampleTree transTree = case transTree of- (TTBirth _ e@Infection {} (t1,t2))- | hasSampledLeaf t1 && hasSampledLeaf t2 -> STBirth e (sampleTree t1,sampleTree t2)- | hasSampledLeaf t1 -> sampleTree t1- | hasSampledLeaf t2 -> sampleTree t2- (TTDeath _ e@(Sampling _ _)) -> STDeath e- (TTDeath _ e@(Catastrophe _ _)) -> STDeath e- _ -> error "ill-formed transmission tree"---- | Recurse through the tree and extract all birth and death events.-sampleTreeEvents' :: SampleTree -> [EpidemicEvent]-sampleTreeEvents' sTree =- case sTree of- (STDeath e) -> [e]- (STBirth e (s1, s2)) -> e : sampleTreeEvents s1 ++ sampleTreeEvents s2---- | The unique events in a sample tree.-sampleTreeEvents :: SampleTree -> [EpidemicEvent]-sampleTreeEvents = nub . sampleTreeEvents'+-- | Run the simulation and return a @SimulationState@ which holds the history+-- of the simulation.+allEvents ::+ (ModelParameters a b, Population b)+ => SimulationRandEvent a b+ -> a+ -> AbsoluteTime+ -> Maybe (b -> Bool) -- ^ predicate for a valid population+ -> SimulationState b+ -> GenIO+ -> IO (SimulationState b)+allEvents _ _ _ _ TerminatedSimulation _ = return TerminatedSimulation+allEvents simRandEvent@(SimulationRandEvent randEvent) modelParams maxTime maybePopPredicate (SimulationState (currTime, currEvents, currPop, currId)) gen =+ if isNothing maybePopPredicate ||+ (isJust maybePopPredicate && fromJust maybePopPredicate currPop)+ then if isInfected currPop+ then do+ (newTime, event, newPop, newId) <-+ randEvent modelParams currTime currPop currId gen+ if newTime < maxTime+ then allEvents+ simRandEvent+ modelParams+ maxTime+ maybePopPredicate+ (SimulationState+ (newTime, event : currEvents, newPop, newId))+ gen+ else return $+ SimulationState+ (maxTime, StoppingTime : currEvents, currPop, currId)+ else return $+ SimulationState+ (currTime, Extinction : currEvents, currPop, currId)+ else return TerminatedSimulation
− src/Epidemic/BDSCOD.hs
@@ -1,180 +0,0 @@-{-# LANGUAGE RecordWildCards #-}--module Epidemic.BDSCOD- ( configuration- , allEvents- , observedEvents- ) where--import Data.List (nub)-import Data.Maybe (fromJust)-import qualified Data.Vector as V-import qualified Data.Vector.Generic as G-import Epidemic-import Epidemic.Types.Events- ( EpidemicEvent(..)- , PointProcessEvents(..)- , ReconstructedTree(..)- , maybeEpidemicTree- , maybeReconstructedTree- , pointProcessEvents- )-import Epidemic.Types.Parameter-import Epidemic.Types.Population-import Epidemic.Utility-import System.Random.MWC-import System.Random.MWC.Distributions (bernoulli, categorical, exponential)---data BDSCODParameters- -- | birth rate, death rate, sampling rate, catastrophe specification, occurrence rate and disaster specification- = BDSCODParameters Rate Rate Rate (Timed Probability) Rate (Timed Probability)--instance ModelParameters BDSCODParameters where- rNaught (BDSCODParameters birthRate deathRate samplingRate _ occurrenceRate _) _ =- Just $ birthRate / (deathRate + samplingRate + occurrenceRate)- eventRate (BDSCODParameters birthRate deathRate samplingRate _ occurrenceRate _) _ =- Just $ birthRate + deathRate + samplingRate + occurrenceRate- birthProb (BDSCODParameters birthRate deathRate samplingRate _ occurrenceRate _) _ =- Just $ birthRate / (birthRate + deathRate + samplingRate + occurrenceRate)--newtype BDSCODPopulation =- BDSCODPopulation People- deriving (Show)--instance Population BDSCODPopulation where- susceptiblePeople _ = Nothing- infectiousPeople (BDSCODPopulation people) = Just people- removedPeople _ = Nothing- isInfected (BDSCODPopulation (People people)) = not $ V.null people---- | Configuration of a birth-death-sampling-occurrence simulation-configuration :: Time -- ^ Duration of the simulation- -> (Rate,Rate,Rate,[(Time,Probability)],Rate,[(Time,Probability)]) -- ^ Birth, Death, Sampling, Catastrophe probability and Occurrence rates- -> Maybe (SimulationConfiguration BDSCODParameters BDSCODPopulation)-configuration maxTime (birthRate, deathRate, samplingRate, catastropheSpec, occurrenceRate, disasterSpec) =- do catastropheSpec' <- asTimed catastropheSpec- disasterSpec' <- asTimed disasterSpec- let bdscodParams =- BDSCODParameters- birthRate- deathRate- samplingRate- catastropheSpec'- occurrenceRate- disasterSpec'- (seedPerson, newId) = newPerson initialIdentifier- bdscodPop = BDSCODPopulation (People $ V.singleton seedPerson)- in return $ SimulationConfiguration bdscodParams bdscodPop newId maxTime---- | Return a random event from the BDSCOD-process given the current state of the process.-randomEvent :: BDSCODParameters -- ^ Parameters of the process- -> Time -- ^ The current time within the process- -> BDSCODPopulation -- ^ The current state of the populaion- -> Integer -- ^ The current state of the identifier generator- -> GenIO -- ^ The current state of the PRNG- -> IO (Time, EpidemicEvent, BDSCODPopulation, Integer)-randomEvent params@(BDSCODParameters br dr sr catastInfo occr disastInfo) currTime currPop@(BDSCODPopulation (People currPeople)) currId gen =- let netEventRate = fromJust $ eventRate params currTime- eventWeights = V.fromList [br, dr, sr, occr]- in do delay <- exponential (fromIntegral (V.length currPeople) * netEventRate) gen- nextTime <- pure $ currTime + delay- if noScheduledEvent currTime nextTime (catastInfo <> disastInfo)- then do eventIx <- categorical eventWeights gen- (selectedPerson, unselectedPeople) <- randomPerson currPeople gen- return $ case eventIx of- 0 -> let (birthedPerson, newId) = newPerson currId- event = Infection nextTime selectedPerson birthedPerson- in ( nextTime- , event- , BDSCODPopulation (People $ V.cons birthedPerson currPeople)- , newId)- 1 -> (nextTime, Removal nextTime selectedPerson, BDSCODPopulation (People unselectedPeople), currId)- 2 -> (nextTime, Sampling nextTime selectedPerson, BDSCODPopulation (People unselectedPeople), currId)- 3 -> (nextTime, Occurrence nextTime selectedPerson, BDSCODPopulation (People unselectedPeople), currId)- _ -> error "no birth, death, sampling, occurrence event selected."-- else if noScheduledEvent currTime nextTime catastInfo- then let (Just (disastTime,disastProb)) = firstScheduled currTime disastInfo- in do (disastEvent,postDisastPop) <- randomDisasterEvent (disastTime,disastProb) currPop gen- return (disastTime,disastEvent,postDisastPop,currId)- else if noScheduledEvent currTime nextTime disastInfo- then let (Just (catastTime,catastProb)) = firstScheduled currTime catastInfo- in do (catastEvent,postCatastPop) <- randomCatastropheEvent (catastTime,catastProb) currPop gen- return (catastTime,catastEvent,postCatastPop,currId)- else let (Just (catastTime,catastProb)) = firstScheduled currTime catastInfo- (Just (disastTime,disastProb)) = firstScheduled currTime disastInfo- in do (scheduledEvent,postEventPop) <- if catastTime < disastTime then- randomCatastropheEvent (catastTime,catastProb) currPop gen else- randomDisasterEvent (disastTime,disastProb) currPop gen- return (min catastTime disastTime,scheduledEvent,postEventPop,currId)----- | Return a randomly sampled Catastrophe event-randomCatastropheEvent :: (Time,Probability) -- ^ Time and probability of sampling in the catastrophe- -> BDSCODPopulation -- ^ The state of the population prior to the catastrophe- -> GenIO- -> IO (EpidemicEvent,BDSCODPopulation)-randomCatastropheEvent (catastTime, rhoProb) (BDSCODPopulation (People currPeople)) gen = do- rhoBernoullis <- G.replicateM (V.length currPeople) (bernoulli rhoProb gen)- let filterZip predicate a b = fst . V.unzip . V.filter predicate $ V.zip a b- sampledPeople = filterZip snd currPeople rhoBernoullis- unsampledPeople = filterZip (not . snd) currPeople rhoBernoullis- in return- ( Catastrophe catastTime (People sampledPeople)- , BDSCODPopulation (People unsampledPeople))---- | Return a randomly sampled Disaster event-randomDisasterEvent :: (Time,Probability) -- ^ Time and probability of sampling in the disaster- -> BDSCODPopulation -- ^ The state of the population prior to the disaster- -> GenIO- -> IO (EpidemicEvent,BDSCODPopulation)-randomDisasterEvent (disastTime, nuProb) (BDSCODPopulation (People currPeople)) gen = do- nuBernoullis <- G.replicateM (V.length currPeople) (bernoulli nuProb gen)- let filterZip predicate a b = fst . V.unzip . V.filter predicate $ V.zip a b- sampledPeople = filterZip snd currPeople nuBernoullis- unsampledPeople = filterZip (not . snd) currPeople nuBernoullis- in return- ( Disaster disastTime (People sampledPeople)- , BDSCODPopulation (People unsampledPeople))--allEvents ::- BDSCODParameters- -> Time- -> (Time, [EpidemicEvent], BDSCODPopulation, Integer)- -> GenIO- -> IO (Time, [EpidemicEvent], BDSCODPopulation, Integer)-allEvents rates maxTime currState@(currTime, currEvents, currPop, currId) gen =- if isInfected currPop- then do- (newTime, event, newPop, newId) <-- randomEvent rates currTime currPop currId gen- if newTime < maxTime- then allEvents- rates- maxTime- (newTime, event : currEvents, newPop, newId)- gen- else return currState- else return currState---- | The events from the nodes of a reconstructed tree __not__ in time sorted--- order.-reconstructedTreeEvents :: ReconstructedTree -> [EpidemicEvent]-reconstructedTreeEvents node = case node of- (RBranch e lt rt) -> e:(reconstructedTreeEvents lt ++ reconstructedTreeEvents rt)- (RLeaf e) -> [e]---- | Just the observable events from a list of all the events that occurred in a--- simulation of the BDSCOD-process. These events are the result of extracting--- the events from the reconstructed tree and getting the point process events--- that make up the unsequenced samples (see `pointProcessEvents` for details on--- this latter data.)-observedEvents :: [EpidemicEvent] -- ^ All of the simulation events- -> Maybe [EpidemicEvent]-observedEvents eEvents = do- epiTree <- maybeEpidemicTree eEvents- reconTree <- maybeReconstructedTree epiTree- let (PointProcessEvents nonReconTreeEvents) = pointProcessEvents epiTree- let reconTreeEvents = reconstructedTreeEvents reconTree- return . sort . nub $ nonReconTreeEvents ++ reconTreeEvents
− src/Epidemic/BirthDeath.hs
@@ -1,97 +0,0 @@-{-# LANGUAGE RecordWildCards #-}--module Epidemic.BirthDeath- ( configuration- , allEvents- ) where--import Epidemic.Types.Parameter-import Epidemic.Types.Population-import Epidemic.Types.Events-import qualified Data.Vector as V-import System.Random.MWC-import System.Random.MWC.Distributions (bernoulli, exponential)--import Epidemic-import Epidemic.Utility--data BDRates =- BDRates Rate Rate--instance ModelParameters BDRates where- rNaught (BDRates birthRate deathRate) _ = Just $ birthRate / deathRate- eventRate (BDRates birthRate deathRate) _ = Just $ birthRate + deathRate- birthProb (BDRates birthRate deathRate) _ = Just $ birthRate / (birthRate + deathRate)--newtype BDPopulation =- BDPopulation People- deriving (Show)--instance Population BDPopulation where- susceptiblePeople _ = Nothing- infectiousPeople (BDPopulation people) = Just people- removedPeople _ = Nothing- isInfected (BDPopulation (People people)) = not $ V.null people---- | Return a BD-process parameters object-birthDeathRates :: Rate -- ^ birth rate- -> Rate -- ^ death rate- -> Maybe BDRates-birthDeathRates birthRate deathRate- | birthRate >= 0 && deathRate >= 0 = Just $ BDRates birthRate deathRate- | otherwise = Nothing---- | Configuration of a birth-death simulation.-configuration :: Time -- ^ Duration of the simulation- -> (Rate, Rate) -- ^ Birth and Death rates- -> Maybe (SimulationConfiguration BDRates BDPopulation)-configuration maxTime (birthRate, deathRate) =- let (seedPerson, newId) = newPerson initialIdentifier- bdPop = BDPopulation (People $ V.singleton seedPerson)- in do maybeBDRates <- birthDeathRates birthRate deathRate- if maxTime > 0 then Just (SimulationConfiguration maybeBDRates bdPop newId maxTime) else Nothing--randomBirthDeathEvent ::- BDRates- -> Time- -> BDPopulation- -> Integer- -> GenIO- -> IO (Time, EpidemicEvent, BDPopulation, Integer)-randomBirthDeathEvent (BDRates br dr) currTime (BDPopulation (People currPeople)) currId gen = do- delay <- exponential (fromIntegral (V.length currPeople) * (br + dr)) gen- isBirth <- bernoulli (br / (br + dr)) gen- (selectedPerson, unselectedPeople) <- randomPerson currPeople gen- return $- if isBirth- then let newTime = currTime + delay- (birthedPerson, newId) = newPerson currId- event = Infection newTime selectedPerson birthedPerson- in ( newTime- , event- , BDPopulation (People $ V.cons birthedPerson currPeople)- , newId)- else let newTime = currTime + delay- event = Removal newTime selectedPerson- in (newTime, event, BDPopulation (People unselectedPeople), currId)--allEvents ::- BDRates- -> Time- -> (Time, [EpidemicEvent], BDPopulation, Integer)- -> GenIO- -> IO (Time, [EpidemicEvent], BDPopulation, Integer)-allEvents rates maxTime currState@(currTime, currEvents, currPop, currId) gen =- if isInfected currPop- then do- (newTime, event, newPop, newId) <-- randomBirthDeathEvent rates currTime currPop currId gen- if newTime < maxTime- then allEvents- rates- maxTime- (newTime, event : currEvents, newPop, newId)- gen- else return currState- else return currState-
− src/Epidemic/BirthDeathSampling.hs
@@ -1,101 +0,0 @@-module Epidemic.BirthDeathSampling- ( configuration- , allEvents- ) where---import Epidemic.Types.Parameter-import Epidemic.Types.Population-import Epidemic.Types.Events-import Data.Maybe (fromJust)-import qualified Data.Vector as V-import System.Random.MWC-import System.Random.MWC.Distributions (categorical, exponential)--import Epidemic-import Epidemic.Utility--data BDSRates =- BDSRates Rate Rate Rate--instance ModelParameters BDSRates where- rNaught (BDSRates birthRate deathRate samplingRate) _ =- Just $ birthRate / (deathRate + samplingRate)- eventRate (BDSRates birthRate deathRate samplingRate) _ =- Just $ birthRate + deathRate + samplingRate- birthProb (BDSRates birthRate deathRate samplingRate) _ =- Just $ birthRate / (birthRate + deathRate + samplingRate)--newtype BDSPopulation =- BDSPopulation People- deriving (Show)--instance Population BDSPopulation where- susceptiblePeople _ = Nothing- infectiousPeople (BDSPopulation people) = Just people- removedPeople _ = Nothing- isInfected (BDSPopulation (People people)) = not $ V.null people--birthDeathSamplingRates :: Rate -> Rate -> Rate -> BDSRates-birthDeathSamplingRates = BDSRates -- birthRate deathRate samplingRate---- | Configuration of a birth-death-sampling simulation.-configuration :: Time -- ^ Duration of the simulation- -> (Rate,Rate,Rate) -- ^ Birth, Death and Sampling rates- -> SimulationConfiguration BDSRates BDSPopulation-configuration maxTime (birthRate, deathRate, samplingRate) =- let bdsRates = birthDeathSamplingRates birthRate deathRate samplingRate- (seedPerson, newId) = newPerson initialIdentifier- bdsPop = BDSPopulation (People $ V.singleton seedPerson)- in SimulationConfiguration bdsRates bdsPop newId maxTime--randomBirthDeathSamplingEvent ::- BDSRates- -> Time- -> BDSPopulation- -> Integer- -> GenIO- -> IO (Time, EpidemicEvent, BDSPopulation, Integer)-randomBirthDeathSamplingEvent bdsRates@(BDSRates br dr sr) currTime (BDSPopulation (People currPeople)) currId gen =- let netEventRate = fromJust $ eventRate bdsRates currTime - eventWeights = V.fromList [br,dr,sr]- in- do delay <- exponential (fromIntegral (V.length currPeople) * netEventRate) gen- eventIx <- categorical eventWeights gen- (selectedPerson, unselectedPeople) <- randomPerson currPeople gen- return $ case eventIx of- 0 -> let newTime = currTime + delay- (birthedPerson, newId) = newPerson currId- event = Infection newTime selectedPerson birthedPerson- in ( newTime- , event- , BDSPopulation (People $ V.cons birthedPerson currPeople)- , newId)- 1 -> let newTime = currTime + delay- event = Removal newTime selectedPerson- in (newTime, event, BDSPopulation (People unselectedPeople), currId)- 2 -> let newTime = currTime + delay- event = Sampling newTime selectedPerson- in (newTime, event, BDSPopulation (People unselectedPeople), currId)- _ -> error "no birth-death-sampling event selected."--allEvents ::- BDSRates- -> Time- -> (Time, [EpidemicEvent], BDSPopulation, Integer)- -> GenIO- -> IO (Time, [EpidemicEvent], BDSPopulation, Integer)-allEvents bdsRates maxTime currState@(currTime, currEvents, currPop, currId) gen =- if isInfected currPop- then do- (newTime, event, newPop, newId) <-- randomBirthDeathSamplingEvent bdsRates currTime currPop currId gen- if newTime < maxTime- then allEvents- bdsRates- maxTime- (newTime, event : currEvents, newPop, newId)- gen- else return currState- else return currState-
− src/Epidemic/BirthDeathSamplingCatastropheOccurrence.hs
@@ -1,136 +0,0 @@-{-# LANGUAGE RecordWildCards #-}--module Epidemic.BirthDeathSamplingCatastropheOccurrence- ( configuration- , allEvents- , observedEvents- ) where--import Epidemic.Types.Population-import Epidemic.Types.Events-import Epidemic.Types.Parameter-import Data.Maybe (fromJust)-import qualified Data.Vector as V-import qualified Data.Vector.Generic as G-import System.Random.MWC-import System.Random.MWC.Distributions (categorical, exponential, bernoulli)--import Epidemic-import Epidemic.Utility---data BDSCOParameters- -- | birth rate, death rate, sampling rate, catastrophe probability and occurrence rate.- = BDSCOParameters Rate Rate Rate (Timed Probability) Rate--instance ModelParameters BDSCOParameters where- rNaught (BDSCOParameters birthRate deathRate samplingRate _ occurrenceRate) _ =- Just $ birthRate / (deathRate + samplingRate + occurrenceRate)- eventRate (BDSCOParameters birthRate deathRate samplingRate _ occurrenceRate) _ =- Just $ birthRate + deathRate + samplingRate + occurrenceRate- birthProb (BDSCOParameters birthRate deathRate samplingRate _ occurrenceRate) _ =- Just $ birthRate / (birthRate + deathRate + samplingRate + occurrenceRate)--newtype BDSCOPopulation =- BDSCOPopulation People- deriving (Show)--instance Population BDSCOPopulation where- susceptiblePeople _ = Nothing- infectiousPeople (BDSCOPopulation people) = Just people- removedPeople _ = Nothing- isInfected (BDSCOPopulation (People people)) = not $ V.null people---- | Configuration of a birth-death-sampling-occurrence simulation-configuration :: Time -- ^ Duration of the simulation- -> (Rate,Rate,Rate,[(Time,Probability)],Rate) -- ^ Birth, Death, Sampling, Catastrophe probability and Occurrence rates- -> Maybe (SimulationConfiguration BDSCOParameters BDSCOPopulation)-configuration maxTime (birthRate, deathRate, samplingRate, catastropheProb, occurrenceRate) = do- catastropheTimedProb <- asTimed catastropheProb- let bdscoParams =- BDSCOParameters- birthRate- deathRate- samplingRate- catastropheTimedProb- occurrenceRate- (seedPerson, newId) = newPerson initialIdentifier- bdscoPop = BDSCOPopulation (People $ V.singleton seedPerson)- in Just $ SimulationConfiguration bdscoParams bdscoPop newId maxTime---- | Return a random event from the BDSCO-process given the current state of the process.-randomBdscoEvent ::- BDSCOParameters -- ^ Parameters of the process- -> Time -- ^ The current time within the process- -> BDSCOPopulation -- ^ The current state of the populaion- -> Integer -- ^ The current state of the identifier generator- -> GenIO -- ^ The current state of the PRNG- -> IO (Time, EpidemicEvent, BDSCOPopulation, Integer)-randomBdscoEvent params@(BDSCOParameters br dr sr catastInfo occr) currTime currPop@(BDSCOPopulation (People people)) currId gen =- let netEventRate = fromJust $ eventRate params currTime- eventWeights = V.fromList [br, dr, sr, occr]- in- do delay <- exponential (fromIntegral (V.length people) * netEventRate) gen- nextEventTime <- pure $ currTime + delay- if noScheduledEvent currTime nextEventTime catastInfo- then do eventIx <- categorical eventWeights gen- (selectedPerson, unselectedPeople) <- randomPerson people gen- return $ case eventIx of- 0 -> let (birthedPerson, newId) = newPerson currId- event = Infection nextEventTime selectedPerson birthedPerson- in ( nextEventTime- , event- , BDSCOPopulation (People $ V.cons birthedPerson people)- , newId)- 1 -> (nextEventTime, Removal nextEventTime selectedPerson, BDSCOPopulation (People unselectedPeople), currId)- 2 -> (nextEventTime, Sampling nextEventTime selectedPerson, BDSCOPopulation (People unselectedPeople), currId)- 3 -> (nextEventTime, Occurrence nextEventTime selectedPerson, BDSCOPopulation (People unselectedPeople), currId)- _ -> error "no birth, death, sampling, occurrence event selected."- else let (Just (catastTime,catastProb)) = firstScheduled currTime catastInfo- in do (catastEvent,postCatastPop) <- randomCatastropheEvent (catastTime,catastProb) currPop gen- return (catastTime,catastEvent,postCatastPop,currId)----- | Return a randomly sampled Catastrophe event-randomCatastropheEvent :: (Time,Probability) -- ^ Time and probability of sampling in the catastrophe- -> BDSCOPopulation -- ^ The state of the population prior to the catastrophe- -> GenIO- -> IO (EpidemicEvent,BDSCOPopulation)-randomCatastropheEvent (catastTime, rhoProb) (BDSCOPopulation (People currPeople)) gen = do- rhoBernoullis <- G.replicateM (V.length currPeople) (bernoulli rhoProb gen)- let filterZip predicate a b = fst . V.unzip . V.filter predicate $ V.zip a b- sampledPeople = filterZip snd currPeople rhoBernoullis- unsampledPeople = filterZip (not . snd) currPeople rhoBernoullis- in return- ( Catastrophe catastTime (People sampledPeople)- , BDSCOPopulation (People unsampledPeople))--allEvents ::- BDSCOParameters- -> Time- -> (Time, [EpidemicEvent], BDSCOPopulation, Integer)- -> GenIO- -> IO (Time, [EpidemicEvent], BDSCOPopulation, Integer)-allEvents rates maxTime currState@(currTime, currEvents, currPop, currId) gen =- if isInfected currPop- then do- (newTime, event, newPop, newId) <-- randomBdscoEvent rates currTime currPop currId gen- if newTime < maxTime- then allEvents- rates- maxTime- (newTime, event : currEvents, newPop, newId)- gen- else return currState- else return currState----- | Just the observable events from a list of all the events in a simulation.-observedEvents :: [EpidemicEvent] -- ^ All of the simulation events- -> [EpidemicEvent]-observedEvents events = sort $ occurrenceEvents ++ sampleTreeEvents''- where- occurrenceEvents = filter isNonReconTreeObservation events- sampleTreeEvents'' =- sampleTreeEvents . sampleTree $ transmissionTree events (Person 1)
− src/Epidemic/BirthDeathSamplingOccurrence.hs
@@ -1,119 +0,0 @@-module Epidemic.BirthDeathSamplingOccurrence- ( configuration- , allEvents- , observedEvents- ) where--import Epidemic.Types.Population-import Epidemic.Types.Events-import Epidemic.Types.Parameter-import Data.Maybe (fromJust)-import qualified Data.Vector as V-import System.Random.MWC-import System.Random.MWC.Distributions (categorical, exponential)--import Epidemic-import Epidemic.Utility--data BDSORates =- BDSORates Rate Rate Rate Rate--instance ModelParameters BDSORates where- rNaught (BDSORates birthRate deathRate samplingRate occurrenceRate) _ =- Just $ birthRate / (deathRate + samplingRate + occurrenceRate)- eventRate (BDSORates birthRate deathRate samplingRate occurrenceRate) _ =- Just $ birthRate + deathRate + samplingRate + occurrenceRate- birthProb (BDSORates birthRate deathRate samplingRate occurrenceRate) _ =- Just $ birthRate / (birthRate + deathRate + samplingRate + occurrenceRate)--newtype BDSOPopulation =- BDSOPopulation People- deriving (Show)--instance Population BDSOPopulation where- susceptiblePeople _ = Nothing- infectiousPeople (BDSOPopulation people) = Just people- removedPeople _ = Nothing- isInfected (BDSOPopulation (People people)) = not $ V.null people--birthDeathSamplingOccurrenceRates :: Rate -> Rate -> Rate -> Rate -> BDSORates-birthDeathSamplingOccurrenceRates = BDSORates -- birthRate deathRate samplingRate occurrenceRate---- | Configuration of a birth-death-sampling-occurrence simulation-configuration :: Time -- ^ Duration of the simulation- -> (Rate,Rate,Rate,Rate) -- ^ Birth, Death, Sampling and Occurrence rates- -> SimulationConfiguration BDSORates BDSOPopulation-configuration maxTime (birthRate, deathRate, samplingRate, occurrenceRate) =- let bdsoRates =- birthDeathSamplingOccurrenceRates- birthRate- deathRate- samplingRate- occurrenceRate- (seedPerson, newId) = newPerson initialIdentifier- bdsoPop = BDSOPopulation (People $ V.singleton seedPerson)- in SimulationConfiguration bdsoRates bdsoPop newId maxTime--randomBirthDeathSamplingOccurrenceEvent ::- BDSORates- -> Time- -> BDSOPopulation- -> Integer- -> GenIO- -> IO (Time, EpidemicEvent, BDSOPopulation, Integer)-randomBirthDeathSamplingOccurrenceEvent rates@(BDSORates br dr sr ocr) currTime (BDSOPopulation (People currPeople)) currId gen =- let netEventRate = fromJust $ eventRate rates currTime- eventWeights = V.fromList [br,dr,sr,ocr]- in- do delay <- exponential (fromIntegral (V.length currPeople) * netEventRate) gen- eventIx <- categorical eventWeights gen- (selectedPerson, unselectedPeople) <- randomPerson currPeople gen- return $ case eventIx of- 0 -> let newTime = currTime + delay- (birthedPerson, newId) = newPerson currId- event = Infection newTime selectedPerson birthedPerson- in ( newTime- , event- , BDSOPopulation (People $ V.cons birthedPerson currPeople)- , newId)- 1 -> let newTime = currTime + delay- event = Removal newTime selectedPerson- in (newTime, event, BDSOPopulation (People unselectedPeople), currId)- 2 -> let newTime = currTime + delay- event = Sampling newTime selectedPerson- in (newTime, event, BDSOPopulation (People unselectedPeople), currId)- 3 -> let newTime = currTime + delay- event = Occurrence newTime selectedPerson- in (newTime, event, BDSOPopulation (People unselectedPeople), currId)- _ -> error "no birth-death-sampling-occurrence event selected."--allEvents ::- BDSORates- -> Time- -> (Time, [EpidemicEvent], BDSOPopulation, Integer)- -> GenIO- -> IO (Time, [EpidemicEvent], BDSOPopulation, Integer)-allEvents rates maxTime currState@(currTime, currEvents, currPop, currId) gen =- if isInfected currPop- then do- (newTime, event, newPop, newId) <-- randomBirthDeathSamplingOccurrenceEvent rates currTime currPop currId gen- if newTime < maxTime- then allEvents- rates- maxTime- (newTime, event : currEvents, newPop, newId)- gen- else return currState- else return currState----- | Just the observable events from a list of all the events in a simulation.-observedEvents :: [EpidemicEvent] -- ^ All of the simulation events- -> [EpidemicEvent]-observedEvents events =- sort $ occurrenceEvents ++ sampleTreeEvents''- where- occurrenceEvents = filter isNonReconTreeObservation events- sampleTreeEvents'' =- sampleTreeEvents . sampleTree $ transmissionTree events (Person 1)
− src/Epidemic/InhomogeneousBD.hs
@@ -1,115 +0,0 @@-{-# LANGUAGE RecordWildCards #-}--module Epidemic.InhomogeneousBD- ( configuration- , allEvents- ) where--import Epidemic.Types.Population-import Epidemic.Types.Parameter-import Epidemic.Types.Events-import Control.Monad (liftM)-import Data.Maybe (fromJust)-import qualified Data.Vector as V-import System.Random.MWC-import System.Random.MWC.Distributions (bernoulli, exponential)-import Epidemic-import Epidemic.Utility--data InhomBDRates =- InhomBDRates (Timed Rate) Rate--instance ModelParameters InhomBDRates where- rNaught (InhomBDRates timedBirthRate deathRate) time =- let birthRate = cadlagValue timedBirthRate time- in liftM (/ deathRate) birthRate- eventRate (InhomBDRates timedBirthRate deathRate) time =- let birthRate = cadlagValue timedBirthRate time- in liftM (+ deathRate) birthRate- birthProb (InhomBDRates timedBirthRate deathRate) time =- liftM (\br -> br / (br + deathRate)) $ cadlagValue timedBirthRate time--newtype InhomBDPop =- InhomBDPop People- deriving (Show)--instance Population InhomBDPop where- susceptiblePeople _ = Nothing- infectiousPeople (InhomBDPop people) = Just people- removedPeople _ = Nothing- isInfected (InhomBDPop people) = not $ nullPeople people---- | Return a BD-process parameters object-inhomBirthDeathRates :: [(Time, Rate)] -- ^ birth rate- -> Rate -- ^ death rate- -> Maybe InhomBDRates-inhomBirthDeathRates tBrPairs deathRate- | all (\x -> 0 < snd x) tBrPairs && deathRate >= 0 = liftM (\tbr -> InhomBDRates tbr deathRate) $ asTimed tBrPairs- | otherwise = Nothing---- | Configuration of a inhomogeneous birth-death simulation.-configuration :: Time -- ^ Duration of the simulation- -> ([(Time,Rate)], Rate) -- ^ Birth and Death rates- -> Maybe (SimulationConfiguration InhomBDRates InhomBDPop)-configuration maxTime (tBrPairs, deathRate) =- let (seedPerson, newId) = newPerson initialIdentifier- bdPop = InhomBDPop (People $ V.singleton seedPerson)- in do maybeIBDRates <- inhomBirthDeathRates tBrPairs deathRate- if maxTime > 0- then Just (SimulationConfiguration maybeIBDRates bdPop newId maxTime)- else Nothing---- | A random event and the state afterwards-randomEvent ::- InhomBDRates -- ^ model parameters- -> Time -- ^ the current time- -> InhomBDPop -- ^ the population- -> Integer -- ^ current identifier- -> GenIO -- ^ PRNG- -> IO (Time, EpidemicEvent, InhomBDPop, Integer)-randomEvent inhomRates@(InhomBDRates brts@(Timed brts') dr) currTime (InhomBDPop (people@(People peopleVec))) currId gen =- let popSize = fromIntegral $ numPeople people :: Double- stepTimes = map fst brts'- stepFunction = fromJust $ asTimed [(t-currTime,popSize * fromJust (eventRate inhomRates t)) | t <- stepTimes]- in do delay <- inhomExponential stepFunction gen- isBirth <- bernoulli (fromJust (birthProb inhomRates (currTime + delay))) gen- (selectedPerson, unselectedPeople) <- randomPerson peopleVec gen- return $- if isBirth- then let newTime = currTime + delay- (birthedPerson, newId) = newPerson currId- event =- Infection newTime selectedPerson birthedPerson- in ( newTime- , event- , InhomBDPop (addPerson birthedPerson people)- , newId)- else let newTime = currTime + delay- event = Removal newTime selectedPerson- in ( newTime- , event- , InhomBDPop (People unselectedPeople)- , currId)---- | The state of the simulation at the time of the last event prior to the--- stopping time.-allEvents ::- InhomBDRates -- ^ model parameters- -> Time -- ^ stopping time- -> (Time, [EpidemicEvent], InhomBDPop, Integer) -- ^ simulation state- -> GenIO -- ^ PRNG- -> IO (Time, [EpidemicEvent], InhomBDPop, Integer)-allEvents rates maxTime currState@(currTime, currEvents, currPop, currId) gen =- if isInfected currPop- then do- (newTime, event, newPop, newId) <-- randomEvent rates currTime currPop currId gen- if newTime < maxTime- then allEvents- rates- maxTime- (newTime, event : currEvents, newPop, newId)- gen- else return currState- else return currState-
− src/Epidemic/InhomogeneousBDS.hs
@@ -1,135 +0,0 @@-{-# LANGUAGE RecordWildCards #-}--module Epidemic.InhomogeneousBDS- ( configuration- , allEvents- , observedEvents- , inhomBDSRates- ) where--import Epidemic.Types.Population-import Epidemic.Types.Events-import Epidemic.Types.Parameter-import Control.Monad (liftM)-import Data.Maybe (fromJust)-import qualified Data.Vector as V-import System.Random.MWC-import System.Random.MWC.Distributions (categorical, exponential)-import Epidemic-import Epidemic.Utility--data InhomBDSRates =- InhomBDSRates (Timed Rate) Rate Rate--instance ModelParameters InhomBDSRates where- rNaught (InhomBDSRates timedBirthRate deathRate sampleRate) time =- let birthRate = cadlagValue timedBirthRate time- in liftM (/ (deathRate + sampleRate)) birthRate- eventRate (InhomBDSRates timedBirthRate deathRate sampleRate) time =- let birthRate = cadlagValue timedBirthRate time- in liftM (+ (deathRate + sampleRate)) birthRate- birthProb (InhomBDSRates timedBirthRate deathRate sampleRate) time =- liftM (\br -> br / (br + deathRate + sampleRate)) $ cadlagValue timedBirthRate time--newtype InhomBDSPop =- InhomBDSPop People- deriving (Show)--instance Population InhomBDSPop where- susceptiblePeople _ = Nothing- infectiousPeople (InhomBDSPop people) = Just people- removedPeople _ = Nothing- isInfected (InhomBDSPop people) = not $ nullPeople people---- | Return a BDS-process parameters object------ Note that this requires that the rates are all positive, if they are not it--- will return @Nothing@.-inhomBDSRates :: [(Time, Rate)] -- ^ birth rate- -> Rate -- ^ death rate- -> Rate -- ^ sample rate- -> Maybe InhomBDSRates-inhomBDSRates tBrPairs deathRate sampleRate- | all (\x -> 0 < snd x) tBrPairs && deathRate >= 0 && sampleRate >= 0 =- (\tbr -> InhomBDSRates tbr deathRate sampleRate) <$> asTimed tBrPairs- | otherwise = Nothing---- | Configuration of a inhomogeneous birth-death-sampling simulation.------ Note that this requires that the timed rates are all positive, if they are--- not it will return @Nothing@ which can lead to cryptic bugs.-configuration :: Time -- ^ Duration of the simulation- -> ([(Time,Rate)], Rate, Rate) -- ^ Birth, Death and Sampling rates- -> Maybe (SimulationConfiguration InhomBDSRates InhomBDSPop)-configuration maxTime (tBrPairs, deathRate, sampleRate) =- let (seedPerson, newId) = newPerson initialIdentifier- bdsPop = InhomBDSPop (People $ V.singleton seedPerson)- in do maybeIBDSRates <- inhomBDSRates tBrPairs deathRate sampleRate- if maxTime > 0- then Just- (SimulationConfiguration maybeIBDSRates bdsPop newId maxTime)- else Nothing---- | A random event and the state afterwards-randomEvent ::- InhomBDSRates -- ^ model parameters- -> Time -- ^ the current time- -> InhomBDSPop -- ^ the population- -> Integer -- ^ current identifier- -> GenIO -- ^ PRNG- -> IO (Time, EpidemicEvent, InhomBDSPop, Integer)-randomEvent inhomRates@(InhomBDSRates brts@(Timed brts') dr sr) currTime (InhomBDSPop (people@(People peopleVec))) currId gen =- let popSize = fromIntegral $ numPeople people :: Double- stepTimes = map fst brts'- stepFunction = fromJust $ asTimed [(t-currTime,popSize * fromJust (eventRate inhomRates t)) | t <- stepTimes]- eventWeights t = V.fromList [fromJust (cadlagValue brts t), dr, sr]- in do delay <- inhomExponential stepFunction gen- eventIx <- categorical (eventWeights (currTime + delay)) gen- (selectedPerson, unselectedPeople) <- randomPerson peopleVec gen- return $ case eventIx of- 0 -> let newTime = currTime + delay- (birthedPerson, newId) = newPerson currId- event = Infection newTime selectedPerson birthedPerson- in ( newTime- , event- , InhomBDSPop (addPerson birthedPerson people)- , newId)- 1 -> let newTime = currTime + delay- event = Removal newTime selectedPerson- in (newTime, event, InhomBDSPop (People unselectedPeople), currId)- 2 -> let newTime = currTime + delay- event = Sampling newTime selectedPerson- in (newTime, event, InhomBDSPop (People unselectedPeople), currId)- _ -> error "no birth-death-sampling event selected."---- | The state of the simulation at the time of the last event prior to the--- stopping time.-allEvents ::- InhomBDSRates -- ^ model parameters- -> Time -- ^ stopping time- -> (Time, [EpidemicEvent], InhomBDSPop, Integer) -- ^ simulation state- -> GenIO -- ^ PRNG- -> IO (Time, [EpidemicEvent], InhomBDSPop, Integer)-allEvents rates maxTime currState@(currTime, currEvents, currPop, currId) gen =- if isInfected currPop- then do- (newTime, event, newPop, newId) <-- randomEvent rates currTime currPop currId gen- if newTime < maxTime- then allEvents- rates- maxTime- (newTime, event : currEvents, newPop, newId)- gen- else return currState- else return currState----- | Just the observable events from a list of all the events in a simulation.-observedEvents :: [EpidemicEvent] -- ^ All of the simulation events- -> [EpidemicEvent]-observedEvents [] = []-observedEvents events = sort $ sampleTreeEvents''- where- sampleTreeEvents'' =- sampleTreeEvents . sampleTree $ transmissionTree events (Person 1)
+ src/Epidemic/Model/BDSCOD.hs view
@@ -0,0 +1,170 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE MultiParamTypeClasses #-}++module Epidemic.Model.BDSCOD+ ( configuration+ , randomEvent+ , BDSCODParameters(..)+ , BDSCODPopulation(..)+ ) where++import Data.List (nub)+import Data.Maybe (fromJust, isJust, isNothing)+import qualified Data.Vector as V+import qualified Data.Vector.Generic as G+import Epidemic+import Epidemic.Types.Events+ ( EpidemicEvent(..)+ , maybeEpidemicTree+ )+import Epidemic.Types.Parameter+import Epidemic.Types.Population+import Epidemic.Types.Simulation+ ( SimulationConfiguration(..)+ , SimulationRandEvent(..)+ )+import Epidemic.Types.Time+ ( AbsoluteTime(..)+ , TimeDelta(..)+ , Timed(..)+ , cadlagValue+ , diracDeltaValue+ , nextTime+ , timeAfterDelta+ , allTimes+ , asTimed+ )+import Epidemic.Utility+import System.Random.MWC+import System.Random.MWC.Distributions (bernoulli, categorical, exponential)++-- | birth rate, death rate, sampling rate, catastrophe specification, occurrence rate and disaster specification+data BDSCODParameters = BDSCODParameters Rate Rate Rate (Timed Probability) Rate (Timed Probability)++data BDSCODPopulation =+ BDSCODPopulation People+ deriving (Show)++instance ModelParameters BDSCODParameters BDSCODPopulation where+ rNaught _ (BDSCODParameters birthRate deathRate samplingRate _ occurrenceRate _) _ =+ Just $ birthRate / (deathRate + samplingRate + occurrenceRate)+ eventRate _ (BDSCODParameters birthRate deathRate samplingRate _ occurrenceRate _) _ =+ Just $ birthRate + deathRate + samplingRate + occurrenceRate+ birthProb _ (BDSCODParameters birthRate deathRate samplingRate _ occurrenceRate _) _ =+ Just $ birthRate / (birthRate + deathRate + samplingRate + occurrenceRate)++instance Population BDSCODPopulation where+ susceptiblePeople _ = Nothing+ infectiousPeople (BDSCODPopulation people) = Just people+ removedPeople _ = Nothing+ isInfected (BDSCODPopulation (People people)) = not $ V.null people++-- | Configuration of a birth-death-sampling-occurrence-disaster simulation+configuration ::+ TimeDelta -- ^ Duration of the simulation+ -> Bool -- ^ condition upon at least two sequenced samples.+ -> ( Rate+ , Rate+ , Rate+ , [(AbsoluteTime, Probability)]+ , Rate+ , [(AbsoluteTime, Probability)]) -- ^ Birth, Death, Sampling, Catastrophe probability, Occurrence rates and Disaster probabilities+ -> Maybe (SimulationConfiguration BDSCODParameters BDSCODPopulation)+configuration maxTime atLeastCherry (birthRate, deathRate, samplingRate, catastropheSpec, occurrenceRate, disasterSpec) = do+ catastropheSpec' <- asTimed catastropheSpec+ disasterSpec' <- asTimed disasterSpec+ let bdscodParams =+ BDSCODParameters+ birthRate+ deathRate+ samplingRate+ catastropheSpec'+ occurrenceRate+ disasterSpec'+ (seedPerson, newId) = newPerson initialIdentifier+ bdscodPop = BDSCODPopulation (People $ V.singleton seedPerson)+ in return $+ SimulationConfiguration+ bdscodParams+ bdscodPop+ newId+ (AbsoluteTime 0)+ maxTime+ Nothing+ atLeastCherry++-- | The way in which random events are generated in this model.+randomEvent :: SimulationRandEvent BDSCODParameters BDSCODPopulation+randomEvent = SimulationRandEvent randomEvent'++-- | Return a random event from the BDSCOD-process given the current state of the process.+randomEvent' :: BDSCODParameters -- ^ Parameters of the process+ -> AbsoluteTime -- ^ The current time within the process+ -> BDSCODPopulation -- ^ The current state of the populaion+ -> Identifier -- ^ The current state of the identifier generator+ -> GenIO -- ^ The current state of the PRNG+ -> IO (AbsoluteTime, EpidemicEvent, BDSCODPopulation, Identifier)+randomEvent' params@(BDSCODParameters br dr sr catastInfo occr disastInfo) currTime currPop@(BDSCODPopulation currPeople) currId gen =+ let netEventRate = fromJust $ eventRate currPop params currTime+ eventWeights = V.fromList [br, dr, sr, occr]+ in do delay <- exponential (fromIntegral (numPeople currPeople) * netEventRate) gen+ let newEventTime = timeAfterDelta currTime (TimeDelta delay)+ if noScheduledEvent currTime newEventTime (catastInfo <> disastInfo)+ then do eventIx <- categorical eventWeights gen+ (selectedPerson, unselectedPeople) <- randomPerson currPeople gen+ return $ case eventIx of+ 0 -> let (birthedPerson, newId) = newPerson currId+ infEvent = Infection newEventTime selectedPerson birthedPerson+ in ( newEventTime+ , infEvent+ , BDSCODPopulation (addPerson birthedPerson currPeople)+ , newId)+ 1 -> (newEventTime, Removal newEventTime selectedPerson, BDSCODPopulation unselectedPeople, currId)+ 2 -> (newEventTime, IndividualSample newEventTime selectedPerson True, BDSCODPopulation unselectedPeople, currId)+ 3 -> (newEventTime, IndividualSample newEventTime selectedPerson False, BDSCODPopulation unselectedPeople, currId)+ _ -> error "no birth, death, sampling, occurrence event selected."++ else if noScheduledEvent currTime newEventTime catastInfo+ then let (Just (disastTime,disastProb)) = firstScheduled currTime disastInfo+ in do (disastEvent,postDisastPop) <- randomDisasterEvent (disastTime,disastProb) currPop gen+ return (disastTime,disastEvent,postDisastPop,currId)+ else if noScheduledEvent currTime newEventTime disastInfo+ then let (Just (catastTime,catastProb)) = firstScheduled currTime catastInfo+ in do (catastEvent,postCatastPop) <- randomCatastropheEvent (catastTime,catastProb) currPop gen+ return (catastTime,catastEvent,postCatastPop,currId)+ else let (Just (catastTime,catastProb)) = firstScheduled currTime catastInfo+ (Just (disastTime,disastProb)) = firstScheduled currTime disastInfo+ in do (scheduledEvent,postEventPop) <- if catastTime < disastTime then+ randomCatastropheEvent (catastTime,catastProb) currPop gen else+ randomDisasterEvent (disastTime,disastProb) currPop gen+ return (min catastTime disastTime,scheduledEvent,postEventPop,currId)+++-- | Return a randomly sampled Catastrophe event+randomCatastropheEvent :: (AbsoluteTime,Probability) -- ^ Time and probability of sampling in the catastrophe+ -> BDSCODPopulation -- ^ The state of the population prior to the catastrophe+ -> GenIO+ -> IO (EpidemicEvent,BDSCODPopulation)+randomCatastropheEvent (catastTime, rhoProb) (BDSCODPopulation (People currPeople)) gen = do+ rhoBernoullis <- G.replicateM (V.length currPeople) (bernoulli rhoProb gen)+ let filterZip predicate a b = fst . V.unzip . V.filter predicate $ V.zip a b+ sampledPeople = filterZip snd currPeople rhoBernoullis+ unsampledPeople = filterZip (not . snd) currPeople rhoBernoullis+ in return+ ( PopulationSample catastTime (People sampledPeople) True+ , BDSCODPopulation (People unsampledPeople))++-- | 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+ -> BDSCODPopulation -- ^ The state of the population prior to the disaster+ -> GenIO+ -> IO (EpidemicEvent,BDSCODPopulation)+randomDisasterEvent (disastTime, nuProb) (BDSCODPopulation (People currPeople)) gen = do+ nuBernoullis <- G.replicateM (V.length currPeople) (bernoulli nuProb gen)+ let filterZip predicate a b = fst . V.unzip . V.filter predicate $ V.zip a b+ sampledPeople = filterZip snd currPeople nuBernoullis+ unsampledPeople = filterZip (not . snd) currPeople nuBernoullis+ in return+ ( PopulationSample disastTime (People sampledPeople) False+ , BDSCODPopulation (People unsampledPeople))
+ src/Epidemic/Model/InhomogeneousBDS.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE MultiParamTypeClasses #-}++module Epidemic.Model.InhomogeneousBDS+ ( configuration+ , randomEvent+ , inhomBDSRates+ , InhomBDSRates(..)+ , InhomBDSPop(..)+ ) where++import Epidemic.Types.Time+ ( AbsoluteTime(..)+ , Timed(..)+ , TimeDelta(..)+ , allTimes+ , asTimed+ , diracDeltaValue+ , nextTime+ , cadlagValue+ , timeAfterDelta+ )+import Control.Monad (liftM)+import Data.Maybe (fromJust, isJust, isNothing)+import qualified Data.Vector as V+import Epidemic+import Epidemic.Types.Events+ ( EpidemicEvent(..)+ , maybeEpidemicTree+ )+import Epidemic.Types.Parameter+import Epidemic.Types.Population+import Epidemic.Types.Observations+import Epidemic.Types.Simulation+ ( SimulationConfiguration(..)+ , SimulationRandEvent(..)+ , SimulationState(..)+ )+import Epidemic.Utility+import System.Random.MWC+import System.Random.MWC.Distributions (categorical, exponential)++data InhomBDSRates =+ InhomBDSRates (Timed Rate) Rate Rate++data InhomBDSPop =+ InhomBDSPop People+ deriving (Show)++instance ModelParameters InhomBDSRates InhomBDSPop where+ rNaught _ (InhomBDSRates timedBirthRate deathRate sampleRate) time =+ let birthRate = cadlagValue timedBirthRate time+ in liftM (/ (deathRate + sampleRate)) birthRate+ eventRate _ (InhomBDSRates timedBirthRate deathRate sampleRate) time =+ let birthRate = cadlagValue timedBirthRate time+ in liftM (+ (deathRate + sampleRate)) birthRate+ birthProb _ (InhomBDSRates timedBirthRate deathRate sampleRate) time =+ liftM (\br -> br / (br + deathRate + sampleRate)) $+ cadlagValue timedBirthRate time++instance Population InhomBDSPop where+ susceptiblePeople _ = Nothing+ infectiousPeople (InhomBDSPop people) = Just people+ removedPeople _ = Nothing+ isInfected (InhomBDSPop people) = not $ nullPeople people++-- | Return a BDS-process parameters object+--+-- Note that this requires that the rates are all positive, if they are not it+-- will return @Nothing@.+inhomBDSRates ::+ Timed Rate -- ^ birth rate+ -> Rate -- ^ death rate+ -> Rate -- ^ sample rate+ -> Maybe InhomBDSRates+inhomBDSRates timedBirthRate@(Timed tBrPairs) deathRate sampleRate+ | all (\x -> 0 < snd x) tBrPairs && deathRate >= 0 && sampleRate >= 0 =+ Just $ InhomBDSRates timedBirthRate deathRate sampleRate+ | otherwise = Nothing++-- | Configuration of a inhomogeneous birth-death-sampling simulation.+--+-- Note that this requires that the timed rates are all positive, if they are+-- not it will return @Nothing@ which can lead to cryptic bugs.+configuration ::+ TimeDelta -- ^ Duration of the simulation after starting at time 0.+ -> Bool -- ^ condition upon at least two sequenced samples.+ -> ([(AbsoluteTime, Rate)], Rate, Rate) -- ^ Birth, Death and Sampling rates+ -> Maybe (SimulationConfiguration InhomBDSRates InhomBDSPop)+configuration maxTime atLeastCherry (tBrPairs, deathRate, sampleRate) =+ let (seedPerson, newId) = newPerson initialIdentifier+ bdsPop = InhomBDSPop (People $ V.singleton seedPerson)+ in do timedBirthRate <- asTimed tBrPairs+ maybeIBDSRates <- inhomBDSRates timedBirthRate deathRate sampleRate+ if maxTime > TimeDelta 0+ then Just+ (SimulationConfiguration+ maybeIBDSRates+ bdsPop+ newId+ (AbsoluteTime 0)+ maxTime+ Nothing+ atLeastCherry)+ else Nothing++randomEvent :: SimulationRandEvent InhomBDSRates InhomBDSPop+randomEvent = SimulationRandEvent randomEvent'++-- | A random event and the state afterwards+randomEvent' ::+ InhomBDSRates -- ^ model parameters+ -> AbsoluteTime -- ^ the current time+ -> InhomBDSPop -- ^ the population+ -> Identifier -- ^ current identifier+ -> GenIO -- ^ PRNG+ -> IO (AbsoluteTime, EpidemicEvent, InhomBDSPop, Identifier)+randomEvent' inhomRates@(InhomBDSRates brts dr sr) currTime pop@(InhomBDSPop (people@(People peopleVec))) currId gen =+ let popSize = fromIntegral $ numPeople people :: Double+ eventWeights t = V.fromList [fromJust (cadlagValue brts t), dr, sr]+ -- we need a new step function to account for the population size.+ (Just stepFunction) =+ asTimed+ [ (t, popSize * fromJust (eventRate pop inhomRates t))+ | t <- allTimes brts+ ]+ in do (Just newEventTime) <- inhomExponential stepFunction currTime gen+ eventIx <- categorical (eventWeights newEventTime) gen+ (selectedPerson, unselectedPeople) <- randomPerson people gen+ return $+ case eventIx of+ 0 ->+ ( newEventTime+ , Infection newEventTime selectedPerson birthedPerson+ , InhomBDSPop (addPerson birthedPerson people)+ , newId)+ where (birthedPerson, newId) = newPerson currId+ 1 ->+ ( newEventTime+ , Removal newEventTime selectedPerson+ , InhomBDSPop unselectedPeople+ , currId)+ 2 ->+ ( newEventTime+ , IndividualSample newEventTime selectedPerson True+ , InhomBDSPop unselectedPeople+ , currId)+ _ -> error "no birth-death-sampling event selected."
+ src/Epidemic/Model/LogisticBDSD.hs view
@@ -0,0 +1,193 @@+{-# 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+ , allTimes+ , diracDeltaValue+ , nextTime+ , cadlagValue+ , timeAfterDelta+ )+import Epidemic.Types.Parameter+ ( ModelParameters(..)+ , Probability+ , Rate+ )+import Epidemic.Types.Population+ ( Identifier(..)+ , People(..)+ , Population(..)+ , addPerson+ , nullPeople+ , numPeople+ )+import Epidemic.Types.Simulation+ ( SimulationConfiguration(..)+ , SimulationRandEvent(..)+ , SimulationState(..)+ )+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@LogisticBDSDParameters {..} absTime = do+ er <- eventRate lpop lparam absTime+ Just $ br / er+ where+ br = logisticBirthRate lparam lpop++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.+ -> (Rate, Int, Rate, Rate, [(AbsoluteTime, Probability)])+ -> Either String (SimulationConfiguration LogisticBDSDParameters LogisticBDSDPopulation)+configuration simDuration atLeastCherry (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)+ in return $+ SimulationConfiguration+ logBDSDParams+ logBDSDPop+ newId+ (AbsoluteTime 0)+ simDuration+ Nothing+ 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+ logisticBR = logisticBirthRate params currPop+ eventWeights =+ V.fromList [logisticBR, paramsDeathRate, paramsSamplingRate]+ in do delay <- exponential (netEventRate * popSizeDouble) gen+ let newEventTime = timeAfterDelta currTime (TimeDelta delay)+ if noScheduledEvent currTime newEventTime paramsDisasters+ then do+ eventIx <- categorical eventWeights 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))
src/Epidemic/Types/Events.hs view
@@ -1,347 +1,169 @@+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveGeneric #-} module Epidemic.Types.Events- ( EpidemicEvent(Infection, Removal, Sampling, Catastrophe,- Occurrence, Disaster)+ ( EpidemicEvent(Infection, Removal, IndividualSample,+ PopulationSample, StoppingTime, Extinction)+ , popSampPeople+ , popSampSeq+ , popSampTime+ , indSampPerson+ , indSampSeq+ , indSampTime , EpidemicTree(Branch, Leaf, Shoot) , maybeEpidemicTree+ , isExtinctionOrStopping , eventTime- , ReconstructedTree(RBranch, RLeaf)- , maybeReconstructedTree- , PointProcessEvents(PointProcessEvents)- , pointProcessEvents , derivedFrom- , Newick- , asNewickString ) where import qualified Data.Aeson as Json import qualified Data.ByteString as B import qualified Data.ByteString.Builder as BBuilder-import qualified Data.Csv as Csv import qualified Data.List as List import qualified Data.Vector as V import Epidemic.Types.Parameter import Epidemic.Types.Population+import Epidemic.Types.Time (AbsoluteTime(..), TimeDelta(..), timeDelta) import GHC.Generics -- | Events that can occur in an epidemic with their absolute time. data EpidemicEvent- = Infection Time Person Person -- ^ infection time, infector, infectee- | Removal Time Person -- ^ removal without observation- | Sampling Time Person -- ^ removal and inclusion in phylogeny- | Catastrophe Time People -- ^ scheduled sampling of lineages- | Occurrence Time Person -- ^ removal and observed by not in phylogeny- | Disaster Time People -- ^ scheduled occurrence of lineages+ = Infection AbsoluteTime Person Person -- ^ absolute time; infector; infectee+ | Removal AbsoluteTime Person+ | IndividualSample+ { indSampTime :: AbsoluteTime+ , indSampPerson :: Person+ , indSampSeq :: Bool+ }+ | PopulationSample+ { popSampTime :: AbsoluteTime+ , popSampPeople :: People+ , popSampSeq :: Bool+ }+ | Extinction -- ^ epidemic went extinct time time can be recovered from the preceeding removal+ | StoppingTime -- ^ the simulation reached the stopping time deriving (Show, Generic, Eq) instance Json.FromJSON EpidemicEvent instance Json.ToJSON EpidemicEvent -instance Csv.ToRecord EpidemicEvent where- toRecord e =- case e of- (Infection time person1 person2) ->- Csv.record- [ "infection"- , Csv.toField time- , Csv.toField person1- , Csv.toField person2- ]- (Removal time person) ->- Csv.record ["removal", Csv.toField time, Csv.toField person, "NA"]- (Sampling time person) ->- Csv.record ["sampling", Csv.toField time, Csv.toField person, "NA"]- (Catastrophe time people) ->- Csv.record ["catastrophe", Csv.toField time, Csv.toField people, "NA"]- (Occurrence time person) ->- Csv.record ["occurrence", Csv.toField time, Csv.toField person, "NA"]- (Disaster time people) ->- Csv.record ["disaster", Csv.toField time, Csv.toField people, "NA"]--et :: B.ByteString -> Csv.Record -> Bool-et bs r = (== bs) . head $ V.toList r--instance Csv.FromRecord EpidemicEvent where- parseRecord r- | et "infection" r =- Infection <$> (r Csv..! 1) <*> (Person <$> (r Csv..! 2)) <*>- (Person <$> (r Csv..! 3))- | et "removal" r = Removal <$> (r Csv..! 1) <*> (Person <$> (r Csv..! 2))- | et "sampling" r = Sampling <$> (r Csv..! 1) <*> (Person <$> (r Csv..! 2))- | et "catastrophe" r = Catastrophe <$> (r Csv..! 1) <*> (r Csv..! 2)- | et "occurrence" r =- Occurrence <$> (r Csv..! 1) <*> (Person <$> (r Csv..! 2))- | et "disaster" r = Disaster <$> (r Csv..! 1) <*> (r Csv..! 2)- | otherwise = undefined+-- | Predicate for whether an @EpidemicEvent@ is one of the terminal events of+-- extinction or the stopping time having been reached.+isExtinctionOrStopping :: EpidemicEvent -> Bool+isExtinctionOrStopping e =+ case e of+ Extinction -> True+ StoppingTime -> True+ _ -> False --- | Epidemic Events are ordered based on which occurred first.+-- | Epidemic Events are ordered based on which occurred first. Since+-- 'Extinction' and 'StoppingTime' events are there as placeholders they are+-- placed as the end of the order. instance Ord EpidemicEvent where+ Extinction <= Extinction = True+ Extinction <= StoppingTime = True+ Extinction <= _ = False+ StoppingTime <= Extinction = False+ StoppingTime <= StoppingTime = True+ StoppingTime <= _ = False e1 <= e2 = eventTime e1 <= eventTime e2 -- | The absolute time an event occurred.-eventTime :: EpidemicEvent -> Time+eventTime :: EpidemicEvent -> AbsoluteTime eventTime e = case e of Infection time _ _ -> time Removal time _ -> time- Sampling time _ -> time- Catastrophe time _ -> time- Occurrence time _ -> time- Disaster time _ -> time+ IndividualSample {..} -> indSampTime+ PopulationSample {..} -> popSampTime -- | The events that occurred as a result of the existance of the given person.-derivedFrom :: Person- -> [EpidemicEvent] -- ^ ordered epidemic events- -> [EpidemicEvent]+derivedFrom ::+ Person+ -> [EpidemicEvent] -- ^ ordered epidemic events+ -> [EpidemicEvent] derivedFrom person = derivedFromPeople (asPeople [person]) -- | The events that occurred as a result of the existance of a group of people-derivedFromPeople :: People- -> [EpidemicEvent] -- ^ ordered epidemic events- -> [EpidemicEvent]+derivedFromPeople ::+ People+ -> [EpidemicEvent] -- ^ ordered epidemic events+ -> [EpidemicEvent] derivedFromPeople _ [] = []-derivedFromPeople people (e:es) = case e of- Infection _ p1 p2 -> if includesPerson people p1 || includesPerson people p2- then let people' = addPerson p2 (addPerson p1 people)- in e : derivedFromPeople people' es- else derivedFromPeople people es- Removal _ p -> let derivedEvents = derivedFromPeople people es- in if includesPerson people p- then e:derivedEvents- else derivedEvents- Sampling _ p -> let derivedEvents = derivedFromPeople people es- in if includesPerson people p- then e:derivedEvents- else derivedEvents- Catastrophe _ ps -> let derivedEvents = derivedFromPeople people es- in if haveCommonPeople people ps- then e:derivedEvents- else derivedEvents- Occurrence _ p -> let derivedEvents = derivedFromPeople people es- in if includesPerson people p- then e:derivedEvents- else derivedEvents- Disaster _ ps -> let derivedEvents = derivedFromPeople people es- in if haveCommonPeople people ps- then e:derivedEvents- else derivedEvents+derivedFromPeople people (e:es) =+ case e of+ Infection _ p1 p2 ->+ if includesPerson people p1 || includesPerson people p2+ then let people' = addPerson p2 (addPerson p1 people)+ in e : derivedFromPeople people' es+ else derivedFromPeople people es+ Removal _ p ->+ let derivedEvents = derivedFromPeople people es+ in if includesPerson people p+ then e : derivedEvents+ else derivedEvents+ IndividualSample {..} ->+ let derivedEvents = derivedFromPeople people es+ in if includesPerson people indSampPerson+ then e : derivedEvents+ else derivedEvents+ PopulationSample {..} ->+ let derivedEvents = derivedFromPeople people es+ in if haveCommonPeople people popSampPeople+ then e : derivedEvents+ else derivedEvents+ Extinction -> derivedFromPeople people es+ StoppingTime -> derivedFromPeople people es -{-| A representation of the whole transmission tree in a realisation of an-epidemic including the unobserved leaves. Lineages that are still extant are-modelled as shoots and contain a `Person` as their data rather than an event.--}+-- | The whole transmission tree including the unobserved leaves. Lineages that+-- are still extant are modelled as /shoots/ and contain a 'Person' as their+-- data rather than an event. data EpidemicTree- = Branch EpidemicEvent EpidemicTree EpidemicTree -- ^ Internal node representing infection event- | Leaf EpidemicEvent -- ^ External node representing removal event- | Shoot Person -- ^ External node representing extant lineages- deriving (Show,Eq)-+ = Branch EpidemicEvent EpidemicTree EpidemicTree+ | Leaf EpidemicEvent+ | Shoot Person+ deriving (Show, Eq) --- | A tree representation of the epidemic events.-maybeEpidemicTree :: [EpidemicEvent] -- ^ ordered epidemic events- -> Maybe EpidemicTree-maybeEpidemicTree [] = Nothing-maybeEpidemicTree [e] = case e of- Catastrophe _ people -> if nullPeople people- then Nothing- else Just (Leaf e)- Disaster _ people -> if nullPeople people- then Nothing- else Just (Leaf e)- Infection _ p1 p2 -> Just (Branch e (Shoot p1) (Shoot p2))- _ -> Just (Leaf e)-maybeEpidemicTree (e:es:ess) =+-- | If possible return an 'EpidemicTree' describing the /sorted/ list of+-- 'EpidemicEvent'.+maybeEpidemicTree ::+ [EpidemicEvent] -- ^ ordered epidemic events+ -> Either String EpidemicTree+maybeEpidemicTree [] =+ Left "There are no EpidemicEvent values to construct a tree with."+maybeEpidemicTree [e] = case e of+ Infection _ p1 p2 -> Right (Branch e (Shoot p1) (Shoot p2))+ Removal {} -> Right (Leaf e)+ IndividualSample {} -> Right (Leaf e)+ PopulationSample {..} ->+ if nullPeople popSampPeople+ then Left "The last event is a PopulationSample with no people sampled"+ else Right (Leaf e)+ Extinction -> Left "Extinction event encountered. It should have been removed"+ StoppingTime -> Left "Stopping time encountered. It should have been removed"+maybeEpidemicTree (e:es) =+ case e of Infection _ p1 p2 ->- let infectorEvents = derivedFrom p1 (es : ess)- infecteeEvents = derivedFrom p2 (es : ess)+ let infectorEvents = derivedFrom p1 es+ infecteeEvents = derivedFrom p2 es in do leftTree <- if null infectorEvents- then Just (Shoot p1)- else maybeEpidemicTree infectorEvents+ then Right (Shoot p1)+ else maybeEpidemicTree infectorEvents rightTree <- if null infecteeEvents- then Just (Shoot p2)- else maybeEpidemicTree infecteeEvents+ then Right (Shoot p2)+ else maybeEpidemicTree infecteeEvents return $ Branch e leftTree rightTree- Catastrophe _ people -> if nullPeople people- then maybeEpidemicTree (es:ess)- else Just (Leaf e)- Disaster _ people -> if nullPeople people- then maybeEpidemicTree (es:ess)- else Just (Leaf e)- _ -> Just (Leaf e)--{-| A representation of the reconstructed tree which is the phylogeny connecting- all the `Sampling` and `Catastrophe` events.--}-data ReconstructedTree- = RBranch EpidemicEvent ReconstructedTree ReconstructedTree- | RLeaf EpidemicEvent- deriving (Show, Eq)----- | A tree representation of the reconstructed phylogeny.-maybeReconstructedTree :: EpidemicTree -> Maybe ReconstructedTree--maybeReconstructedTree Shoot{} = Nothing--maybeReconstructedTree (Leaf e) = case e of- Sampling{} -> Just $ RLeaf e- Catastrophe{} -> Just $ RLeaf e- _ -> Nothing--maybeReconstructedTree (Branch e@Infection{} lt rt)- | hasSequencedLeaf lt && hasSequencedLeaf rt =- do- rlt <- maybeReconstructedTree lt- rrt <- maybeReconstructedTree rt- Just $ RBranch e rlt rrt- | hasSequencedLeaf lt = maybeReconstructedTree lt- | hasSequencedLeaf rt = maybeReconstructedTree rt- | otherwise = Nothing-maybeReconstructedTree Branch{} = Nothing---- | Predicate for whether an `EpidemicTree` has a leaf which corresponds to a--- node in the `ReconstructedTree`.-hasSequencedLeaf :: EpidemicTree -> Bool--hasSequencedLeaf Shoot {} = False--hasSequencedLeaf (Leaf e) =- case e of- Sampling {} -> True- Catastrophe {} -> True- _ -> False--hasSequencedLeaf (Branch _ lt rt) = hasSequencedLeaf lt || hasSequencedLeaf rt---{-| A representation of the events that can be observed in an epidemic but which- are not included in the reconstructed tree, i.e. the `Occurrence` and- `Disaster` events.--}-newtype PointProcessEvents = PointProcessEvents [EpidemicEvent]---- | Extract the events from an epidemic tree which are observed but not part of--- the reconstructed tree.-pointProcessEvents :: EpidemicTree -> PointProcessEvents--pointProcessEvents Shoot {} = PointProcessEvents []--pointProcessEvents (Leaf e) = case e of- Occurrence {} -> PointProcessEvents [e]- Disaster {} -> PointProcessEvents [e]- _ -> PointProcessEvents []--pointProcessEvents (Branch _ lt rt) =- let (PointProcessEvents lEs) = pointProcessEvents lt- (PointProcessEvents rEs) = pointProcessEvents rt- allEs = List.sort $ lEs ++ rEs- in PointProcessEvents allEs--class Newick t where- -- | Return a representation of the tree in Newick format.- asNewickString :: (Time,Person) -> t -> Maybe (BBuilder.Builder, [EpidemicEvent])---ampersandBuilder :: BBuilder.Builder-ampersandBuilder = BBuilder.charUtf8 '&'--colonBuilder :: BBuilder.Builder-colonBuilder = BBuilder.charUtf8 ':'--leftBraceBuilder :: BBuilder.Builder-leftBraceBuilder = BBuilder.charUtf8 '('--rightBraceBuilder :: BBuilder.Builder-rightBraceBuilder = BBuilder.charUtf8 ')'--commaBuilder :: BBuilder.Builder-commaBuilder = BBuilder.charUtf8 ','--catastrophePeopleBuilder :: People -> BBuilder.Builder-catastrophePeopleBuilder (People persons) =- mconcat $- List.intersperse ampersandBuilder [personByteString p | p <- V.toList persons]---instance Newick EpidemicTree where- asNewickString (_, p) (Shoot p') =- if p /= p'- then Nothing- else let identifier = personByteString p- bl = BBuilder.stringUtf8 "Infinity"- in Just (identifier <> colonBuilder <> bl, [])-- asNewickString (t, p) (Leaf e) =- let identifier = personByteString p- bl a b = BBuilder.doubleDec $ b - a- in case e of- Infection {} -> Nothing- (Removal t' p') ->- if p /= p'- then Nothing- else Just (identifier <> colonBuilder <> bl t t', [e])- (Sampling t' p') ->- if p /= p'- then Nothing- else Just (identifier <> colonBuilder <> bl t t', [e])- (Catastrophe t' ps) ->- if ps `includesPerson` p- then Just (identifier <> colonBuilder <> bl t t', [e])- else Nothing- (Occurrence t' p') ->- if p /= p'- then Nothing- else Just (identifier <> colonBuilder <> bl t t', [e])- (Disaster t' ps) ->- if ps `includesPerson` p- then Just (identifier <> colonBuilder <> bl t t', [e])- else Nothing-- asNewickString (t, p) (Branch e lt rt) =- case e of- (Infection t' p1 p2) ->- if p /= p1- then Nothing- else do- (leftNS, leftEs) <- asNewickString (t', p1) lt- (rightNS, rightEs) <- asNewickString (t', p2) rt- let bl = BBuilder.doubleDec $ t' - t- return- ( leftBraceBuilder <>- leftNS <>- commaBuilder <> rightNS <> rightBraceBuilder <> colonBuilder <> bl- , List.sort $ leftEs ++ rightEs)- _ -> Nothing----instance Newick ReconstructedTree where- asNewickString (t, _) (RLeaf e) =- let bl a b = BBuilder.doubleDec $ b - a- in case e of- (Sampling t' p) -> Just ((personByteString p) <> colonBuilder <> bl t t', [e])- Infection {} -> Nothing- Removal {} -> Nothing- (Catastrophe t' ps) -> Just (catastrophePeopleBuilder ps <> colonBuilder <> bl t t', [e])- Occurrence {} -> Nothing- Disaster {} -> Nothing-- asNewickString (t, _) (RBranch e lt rt) =- case e of- (Infection t' p1 p2) ->- do- (leftNS, leftEs) <- asNewickString (t', p1) lt- (rightNS, rightEs) <- asNewickString (t', p2) rt- let bl = BBuilder.doubleDec $ t' - t- return- ( leftBraceBuilder <>- leftNS <>- commaBuilder <> rightNS <> rightBraceBuilder <> colonBuilder <> bl- , List.sort $ leftEs ++ rightEs)- _ -> Nothing+ Removal {} -> Right (Leaf e)+ IndividualSample {} -> Right (Leaf e)+ PopulationSample {..} ->+ if nullPeople popSampPeople+ then maybeEpidemicTree es+ else Right (Leaf e)+ Extinction -> Left "Extinction event encountered. It should have been removed"+ StoppingTime -> Left "Stopping time encountered. It should have been removed"
+ src/Epidemic/Types/Newick.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE RecordWildCards #-}++module Epidemic.Types.Newick where++import qualified Data.Aeson as Json+import qualified Data.ByteString as B+import qualified Data.ByteString.Builder as BBuilder+import qualified Data.List as List+import qualified Data.Vector as V+import Epidemic.Types.Parameter+import Epidemic.Types.Observations+import Epidemic.Types.Events+import Epidemic.Types.Population+import Epidemic.Types.Time+import GHC.Generics++-- | Class of types that can be expressed in Newick format.+class Newick t+ where+ asNewickString ::+ (AbsoluteTime, Person) -- ^ The person and time of the root of the tree+ -> t+ -> Maybe (BBuilder.Builder, [EpidemicEvent])++ampersandBuilder :: BBuilder.Builder+ampersandBuilder = BBuilder.charUtf8 '&'++colonBuilder :: BBuilder.Builder+colonBuilder = BBuilder.charUtf8 ':'++leftBraceBuilder :: BBuilder.Builder+leftBraceBuilder = BBuilder.charUtf8 '('++rightBraceBuilder :: BBuilder.Builder+rightBraceBuilder = BBuilder.charUtf8 ')'++commaBuilder :: BBuilder.Builder+commaBuilder = BBuilder.charUtf8 ','++catastrophePeopleBuilder :: People -> BBuilder.Builder+catastrophePeopleBuilder (People persons) =+ mconcat $+ List.intersperse ampersandBuilder [personByteString p | p <- V.toList persons]++instance Newick ReconstructedTree where+ asNewickString (t, _) (RLeaf (Observation e)) =+ let branchLength a b = BBuilder.doubleDec td+ where+ (TimeDelta td) = timeDelta a b+ in case e of+ IndividualSample {..} ->+ if indSampSeq+ then Just+ ( (personByteString indSampPerson) <>+ colonBuilder <> branchLength t indSampTime+ , [e])+ else Nothing+ PopulationSample {..} ->+ if popSampSeq+ then Just+ ( catastrophePeopleBuilder popSampPeople <>+ colonBuilder <> branchLength t popSampTime+ , [e])+ else Nothing+ _ -> Nothing+ asNewickString (t, _) (RBranch (Observation e) lt rt) =+ case e of+ (Infection t' p1 p2) -> do+ (leftNS, leftEs) <- asNewickString (t', p1) lt+ (rightNS, rightEs) <- asNewickString (t', p2) rt+ let branchLength = BBuilder.doubleDec td+ where+ (TimeDelta td) = timeDelta t t'+ return+ ( leftBraceBuilder <>+ leftNS <>+ commaBuilder <>+ rightNS <> rightBraceBuilder <> colonBuilder <> branchLength+ , List.sort $ leftEs ++ rightEs)+ _ -> Nothing
src/Epidemic/Types/Observations.hs view
@@ -1,2 +1,122 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveGeneric #-} -module Epidemic.Types.Observations where+module Epidemic.Types.Observations+ ( Observation(..)+ , ReconstructedTree(..)+ , maybeReconstructedTree+ , PointProcessEvents(..)+ , pointProcessEvents+ , reconstructedTreeEvents+ , observedEvents+ ) where++import Control.Monad (liftM)+import qualified Data.Aeson as Json+import qualified Data.ByteString.Builder as BBuilder+import qualified Data.List as List+import qualified Data.Vector as V+import Epidemic.Types.Events+ ( EpidemicEvent(..)+ , EpidemicTree(..)+ , maybeEpidemicTree+ )+import Epidemic.Types.Time (TimeDelta(..), timeDelta)+import Epidemic.Types.Population (People(..), personByteString)+import GHC.Generics++-- | A wrapper for an 'EpidemicEvent' to indicate that this is an even that was+-- observed rather than just an event of the epidemic process.+newtype Observation =+ Observation EpidemicEvent+ deriving (Show, Ord, Eq, Generic)++instance Json.FromJSON Observation++instance Json.ToJSON Observation++-- | A representation of the events that can be observed in an epidemic but+-- which are not included in the reconstructed tree, ie the unsequenced+-- observations.+newtype PointProcessEvents =+ PointProcessEvents [Observation]++-- | Extract the events from an epidemic tree which are observed but not part of+-- the reconstructed tree, ie the ones that are not sequenced.+pointProcessEvents :: EpidemicTree -> PointProcessEvents+pointProcessEvents Shoot {} = PointProcessEvents []+pointProcessEvents (Leaf e) =+ case e of+ IndividualSample {..} -> PointProcessEvents $ if not indSampSeq then [Observation e] else []+ PopulationSample {..} -> PointProcessEvents $ if not popSampSeq then [Observation e] else []+ _ -> PointProcessEvents []+pointProcessEvents (Branch _ lt rt) =+ let (PointProcessEvents lEs) = pointProcessEvents lt+ (PointProcessEvents rEs) = pointProcessEvents rt+ allEs = List.sort $ lEs ++ rEs+ in PointProcessEvents allEs++-- | A representation of the reconstructed tree, ie the tree where the leaves+-- correspond to sequenced observations.+data ReconstructedTree+ = RBranch Observation ReconstructedTree ReconstructedTree+ | RLeaf Observation+ deriving (Show, Eq)++-- | The reconstructed phylogeny obtained by pruning an 'EpidemicTree' which+-- contains represents the transmission tree of the epidemic. In the case where+-- there are no sequenced samples in the epidemic then there is no tree to+-- reconstruct which is why this function is in the either monad.+maybeReconstructedTree :: EpidemicTree -> Either String ReconstructedTree+maybeReconstructedTree Shoot {} = Left "EpidemicTree is only a Shoot"+maybeReconstructedTree (Leaf e) =+ case e of+ IndividualSample {..} -> if indSampSeq+ then Right $ RLeaf (Observation e)+ else Left "Leaf with non-sequenced event individual sample"+ PopulationSample {..} -> if popSampSeq+ then Right $ RLeaf (Observation e)+ else Left "Leaf with non-sequenced event population sample"+ _ -> Left "Bad leaf in the EpidemicTree"+maybeReconstructedTree (Branch e@Infection {} lt rt)+ | hasSequencedLeaf lt && hasSequencedLeaf rt = do+ rlt <- maybeReconstructedTree lt+ rrt <- maybeReconstructedTree rt+ Right $ RBranch (Observation e) rlt rrt+ | hasSequencedLeaf lt = maybeReconstructedTree lt+ | hasSequencedLeaf rt = maybeReconstructedTree rt+ | otherwise = Left "Neither subtree has a sequenced leaf"+maybeReconstructedTree Branch {} = Left "EpidemicTree is a bad branch"++-- | Predicate for whether an 'EpidemicTree' has any leaf which corresponds to a+-- sequenced observation and hence should be included in a @ReconstructedTree@.+hasSequencedLeaf :: EpidemicTree -> Bool+hasSequencedLeaf Shoot {} = False+hasSequencedLeaf (Leaf e) =+ case e of+ IndividualSample {..} -> indSampSeq+ PopulationSample {..} -> popSampSeq+ _ -> False+hasSequencedLeaf (Branch _ lt rt) = hasSequencedLeaf lt || hasSequencedLeaf rt++-- | The events that were observed during the epidemic, ie those in the+-- reconstructed tree and any unsequenced samples. If this is not possible an+-- error message will be returned.+observedEvents :: [EpidemicEvent] -> Either String [Observation]+observedEvents epiEvents = do+ epiTree <- maybeEpidemicTree epiEvents+ let (PointProcessEvents unseqObss) = pointProcessEvents epiTree+ reconTreeEvents <-+ if hasSequencedLeaf epiTree+ then (liftM reconstructedTreeEvents) $ maybeReconstructedTree epiTree+ else Right []+ return $ List.sort . List.nub $ unseqObss ++ reconTreeEvents++-- | A sorted list of all of the observations in the reconstructed tree.+reconstructedTreeEvents :: ReconstructedTree -> [Observation]+reconstructedTreeEvents rt =+ case rt of+ RBranch obs rtl rtr ->+ List.sort $+ obs : (reconstructedTreeEvents rtl ++ reconstructedTreeEvents rtr)+ RLeaf obs -> [obs]
src/Epidemic/Types/Parameter.hs view
@@ -1,84 +1,18 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE MultiParamTypeClasses #-} module Epidemic.Types.Parameter where -import qualified Data.Aeson as Json-import qualified Data.List as List-import qualified Data.Maybe as Maybe-import GHC.Generics--type Time = Double---- | Type containing values at times. The times are increasing as required by--- @asTimed@.-newtype Timed a =- Timed [(Time, a)]- deriving (Generic, Eq, Show)--instance Json.FromJSON a => Json.FromJSON (Timed a)--instance Json.ToJSON a => Json.ToJSON (Timed a)+import Epidemic.Types.Population (Population(..))+import Epidemic.Types.Time (AbsoluteTime(..)) -instance Semigroup (Timed a) where- (Timed x) <> (Timed y) = Timed $ List.sortOn fst (x ++ y)+-- | Class of types that can be considered parameterisations of a epidemic+-- model.+class (Population p) => ModelParameters a p where+ rNaught :: p -> a -> AbsoluteTime -> Maybe Double+ eventRate :: p -> a -> AbsoluteTime -> Maybe Rate+ birthProb :: p -> a -> AbsoluteTime -> Maybe Probability type Rate = Double type Probability = Double---- | Construct a timed list if possible.-asTimed :: Num a- => [(Time,a)] -- ^ list of ascending times and values- -> Maybe (Timed a)-asTimed tas = if isAscending $ map fst tas then Just (Timed $ tas ++ [(1e100,-1)]) else Nothing---- | Predicate to check if a list of orderable objects is in ascending order.-isAscending :: Ord a => [a] -> Bool-isAscending xs = case xs of- [] -> True- [_] -> True- (x:y:xs') -> x <= y && isAscending (y:xs')---- | Evaluate the timed object treating it as a cadlag function-cadlagValue :: Timed a -> Time -> Maybe a-cadlagValue (Timed txs) = cadlagValue' txs---cadlagValue' :: [(Time,a)] -> Time -> Maybe a-cadlagValue' [] _ = Nothing-cadlagValue' ((t, x):txs) q =- if q < t- then Nothing- else let nextCLV = cadlagValue' txs q- in if Maybe.isNothing nextCLV- then Just x- else nextCLV----- | Evaluate the timed object treating it as a direct delta function-diracDeltaValue :: Timed a -> Time -> Maybe a-diracDeltaValue (Timed txs) = diracDeltaValue' txs--diracDeltaValue' :: [(Time,a)] -> Time -> Maybe a-diracDeltaValue' txs q = case txs of- ((t,x):txs') -> if t == q then Just x else diracDeltaValue' txs' q- [] -> Nothing---- | Check if there exists a pair with a particular time index.-hasTime :: Timed a -> Time -> Bool-hasTime (Timed txs) = hasTime' txs--hasTime' :: [(Time,a)] -> Time -> Bool-hasTime' txs q = case txs of- ((t,_):txs') -> t == q || hasTime' txs' q- [] -> False---- | Return the value of the next time if possible or an exact match if it--- exists.-nextTime :: Timed a -> Time -> Maybe Time-nextTime (Timed txs) = nextTime' txs--nextTime' :: [(Time,a)] -> Time -> Maybe Time-nextTime' txs q = case txs of- ((t,_):txs') -> if q < t then Just t else nextTime' txs' q- [] -> Nothing
src/Epidemic/Types/Population.hs view
@@ -4,6 +4,8 @@ module Epidemic.Types.Population ( Person(Person) , People(People)+ , Population(..)+ , Identifier(Identifier) , asPeople , includesPerson , haveCommonPeople@@ -18,24 +20,35 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Builder as BBuilder import Data.ByteString.Internal (c2w)-import qualified Data.Csv as Csv import qualified Data.Vector as V import GHC.Generics +-- | Class of types that can represent populations in an epidemic simulation.+class Population a where+ susceptiblePeople :: a -> Maybe People+ infectiousPeople :: a -> Maybe People+ removedPeople :: a -> Maybe People+ isInfected :: a -> Bool++-- | A type to hold an integer which is unique to each 'Person'.+newtype Identifier =+ Identifier Integer+ deriving (Show, Generic, Eq)++instance Json.FromJSON Identifier++instance Json.ToJSON Identifier++-- | A type to represent a single person in a group of 'People' newtype Person =- Person Integer+ Person Identifier deriving (Show, Generic, Eq) instance Json.FromJSON Person instance Json.ToJSON Person -instance Csv.ToField Person where- toField (Person n) = Csv.toField n--instance Csv.FromField Person where- parseField f = Person <$> (Csv.parseField f :: Csv.Parser Integer)-+-- | A type to represent a population. newtype People = People (V.Vector Person) deriving (Show, Eq, Generic)@@ -44,14 +57,6 @@ instance Json.ToJSON People -instance Csv.ToField People where- toField (People persons) =- B.intercalate ":" $ V.toList $ V.map Csv.toField persons--instance Csv.FromField People where- parseField f =- (People . V.fromList) <$> (mapM Csv.parseField $ B.split (c2w ':') f)- -- | A list of persons as a people asPeople :: [Person] -> People asPeople persons = People $ V.fromList persons@@ -82,4 +87,4 @@ -- | A bytestring builder for a person personByteString :: Person -> BBuilder.Builder-personByteString (Person n) = BBuilder.integerDec n+personByteString (Person (Identifier n)) = BBuilder.integerDec n
+ src/Epidemic/Types/Simulation.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE GADTs #-}++module Epidemic.Types.Simulation+ ( SimulationConfiguration(..)+ , SimulationState(..)+ , SimulationRandEvent(..)+ ) where++import Epidemic.Types.Events+import Epidemic.Types.Parameter+import Epidemic.Types.Population+import Epidemic.Types.Time (AbsoluteTime(..), TimeDelta(..), timeDelta)+import System.Random.MWC++data SimulationConfiguration r p =+ SimulationConfiguration+ { -- | The event rates+ scRates :: r+ -- | The population+ , scPopulation :: p+ -- | A new identifier+ , scNewIdentifier :: Identifier+ -- | The absolute time at which the simulation starts+ , scStartTime :: AbsoluteTime+ -- | The duration of the simulation until it stops+ , scSimDuration :: TimeDelta+ -- | The simulation terminates if this predicate is not satisfied+ , scValidPopulation :: Maybe (p -> Bool)+ -- | The simulation requires at least two sequenced samples+ , scRequireCherry :: Bool+ }++-- | Either there is a valid simulation state which contains a sequence of+-- epidemic events of there is a terminated simulation which indicates that+-- the simulation has been rejected.+data SimulationState b+ = SimulationState (AbsoluteTime, [EpidemicEvent], b, Identifier)+ | TerminatedSimulation+ deriving (Eq, Show)++data SimulationRandEvent a b where+ SimulationRandEvent+ :: (ModelParameters a b, Population b)+ => (a+ -> AbsoluteTime+ -> b+ -> Identifier+ -> GenIO+ -> IO (AbsoluteTime, EpidemicEvent, b, Identifier))+ -> SimulationRandEvent a b
+ src/Epidemic/Types/Time.hs view
@@ -0,0 +1,155 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveGeneric #-}++module Epidemic.Types.Time+ ( AbsoluteTime(..)+ , TimeDelta(..)+ , Timed(..)+ , timeDelta+ , diracDeltaValue+ , timeAfterDelta+ , nextTime+ , cadlagValue+ , isAscending+ , hasTime+ , allTimes+ , asTimed+ ) where++import qualified Data.Aeson as Json+import qualified Data.List as List+import qualified Data.Maybe as Maybe+import GHC.Generics++-- | Absolute time.+newtype AbsoluteTime =+ AbsoluteTime Double+ deriving (Generic, Eq, Show, Ord)++instance Json.FromJSON AbsoluteTime++instance Json.ToJSON AbsoluteTime++-- | Predicate for an infinite absolute time+isInfiniteAbsoluteTime :: AbsoluteTime -> Bool+isInfiniteAbsoluteTime (AbsoluteTime t) = isInfinite t++-- | Duration of time between two absolute times.+newtype TimeDelta =+ TimeDelta Double+ deriving (Generic, Eq, Show, Ord)++instance Json.FromJSON TimeDelta++instance Json.ToJSON TimeDelta++-- | The duration of time between two absolute times+--+-- >>> timeDelta (AbsoluteTime 1) (AbsoluteTime 2.5)+-- TimeDelta 1.5+--+timeDelta ::+ AbsoluteTime -- ^ start+ -> AbsoluteTime -- ^ finish+ -> TimeDelta+timeDelta (AbsoluteTime t0) (AbsoluteTime t1) = TimeDelta (t1 - t0)++-- | The time after a given delay+--+-- >>> timeAfterDelta (AbsoluteTime 1) (TimeDelta 2.5)+-- AbsoluteTime 3.5+--+timeAfterDelta :: AbsoluteTime -> TimeDelta -> AbsoluteTime+timeAfterDelta (AbsoluteTime t0) (TimeDelta d) = AbsoluteTime (t0 + d)++-- | Type containing values at times. The times are increasing as required by+-- @asTimed@.+newtype Timed a =+ Timed [(AbsoluteTime, a)]+ deriving (Generic, Eq, Show)++instance Json.FromJSON a => Json.FromJSON (Timed a)++instance Json.ToJSON a => Json.ToJSON (Timed a)++instance Semigroup (Timed a) where+ (Timed x) <> (Timed y) = Timed $ List.sortOn fst (x ++ y)++-- | Construct a timed list if possible.+asTimed ::+ Num a+ => [(AbsoluteTime, a)] -- ^ list of ascending times and values+ -> Maybe (Timed a)+asTimed tas =+ if isAscending $ map fst tas+ then Just (Timed $ tas ++ [(AbsoluteTime (1 / 0), -1)])+ else Nothing++-- | Predicate to check if a list of orderable objects is in ascending order.+isAscending :: Ord a => [a] -> Bool+isAscending xs =+ case xs of+ [] -> True+ [_] -> True+ (x:y:xs') -> x <= y && isAscending (y : xs')++-- | Evaluate the timed object treating it as a cadlag function+cadlagValue :: Timed a -> AbsoluteTime -> Maybe a+cadlagValue (Timed txs) = cadlagValue' txs++cadlagValue' :: [(AbsoluteTime, a)] -> AbsoluteTime -> Maybe a+cadlagValue' [] _ = Nothing+cadlagValue' ((t, x):txs) q =+ if q < t+ then Nothing+ else let nextCLV = cadlagValue' txs q+ in if Maybe.isNothing nextCLV+ then Just x+ else nextCLV++-- | Evaluate the timed object treating it as a direct delta function+diracDeltaValue :: Timed a -> AbsoluteTime -> Maybe a+diracDeltaValue (Timed txs) = diracDeltaValue' txs++diracDeltaValue' :: [(AbsoluteTime, a)] -> AbsoluteTime -> Maybe a+diracDeltaValue' txs q =+ case txs of+ ((t, x):txs') ->+ if t == q+ then Just x+ else diracDeltaValue' txs' q+ [] -> Nothing++-- | Check if there exists a pair with a particular time index.+hasTime :: Timed a -> AbsoluteTime -> Bool+hasTime (Timed txs) = hasTime' txs++hasTime' :: [(AbsoluteTime, a)] -> AbsoluteTime -> Bool+hasTime' txs q =+ case txs of+ ((t, _):txs') -> t == q || hasTime' txs' q+ [] -> False++-- | Return the value of the next time if possible or an exact match if it+-- exists.+nextTime :: Timed a -> AbsoluteTime -> Maybe AbsoluteTime+nextTime (Timed txs) = nextTime' txs++nextTime' :: [(AbsoluteTime, a)] -> AbsoluteTime -> Maybe AbsoluteTime+nextTime' txs q =+ case txs of+ ((t, _):txs') ->+ if q < t+ then Just t+ else nextTime' txs' q+ [] -> Nothing++-- | Return a list of the (finite) absolute times that the step function changes+-- value.+--+-- >>> let demoMaybeTimed = asTimed [(AbsoluteTime 1,2),(AbsoluteTime 1.5,1)]+-- >>> liftM allTimes demoMaybeTimed+-- Just [AbsoluteTime 1.0,AbsoluteTime 1.5]+--+allTimes :: Timed a -> [AbsoluteTime]+allTimes (Timed txs) = [t | (t, _) <- txs, not $ isInfiniteAbsoluteTime t]
src/Epidemic/Utility.hs view
@@ -1,39 +1,41 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-}+ module Epidemic.Utility where -import Epidemic.Types.Events-import Epidemic.Types.Parameter-import Epidemic.Types.Population+import Control.Applicative import Control.Monad (liftM)-import qualified Data.List as List-import qualified Data.Maybe as Maybe+import Control.Monad.Primitive (PrimMonad, PrimState) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as Char8-import GHC.Generics (Generic)+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(..)+ , Timed(..)+ , TimeDelta(..)+ , diracDeltaValue+ , nextTime+ , cadlagValue+ , timeAfterDelta+ )+import GHC.Generics (Generic) import System.Random.MWC-import System.Random.MWC.Distributions-import Control.Monad.Primitive (PrimMonad, PrimState)-import Control.Applicative-import Text.Trifecta+import System.Random.MWC.Distributions (exponential) -import Epidemic -data SimulationConfiguration r p =- SimulationConfiguration- { rates :: r- , population :: p- , newIdentifier :: Integer- , timeLimit :: Time- }--initialIdentifier :: Integer-initialIdentifier = 1+initialIdentifier :: Identifier+initialIdentifier = Identifier 1 -newPerson :: Integer -> (Person, Integer)-newPerson identifier = (Person identifier, identifier + 1)+newPerson :: Identifier -> (Person, Identifier)+newPerson idntty@(Identifier idInt) = (Person idntty, Identifier (idInt + 1)) selectElem :: V.Vector a -> Int -> (a, V.Vector a) selectElem v n@@ -42,193 +44,195 @@ let (foo, bar) = V.splitAt n v in (V.head bar, foo V.++ (V.tail bar)) -randomPerson :: V.Vector Person -> GenIO -> IO (Person, V.Vector Person)-randomPerson persons gen = do+randomPerson :: People -> GenIO -> IO (Person, People)+randomPerson people@(People persons) gen = do u <- uniform gen- return $ selectElem persons (floor (u * numPersons))- where- numPersons = fromIntegral $ V.length persons :: Double-+ 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)+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)+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)+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)+data NTree =+ NTree [NBranch]+ deriving (Eq) instance Show NTree where show (NTree bs) = show (NBranchSet bs) ++ ";" --- Name → empty | string-newickName :: (Monad f, CharParsing f) => f NName-newickName = optional (some alphaNum) >>= pure---- Leaf → Name-newickLeaf :: (Monad f, CharParsing f) => f NSubtree-newickLeaf = do- n <- newickName- pure (NLeaf n)---- Length → empty | ":" number-newickLength :: (TokenParsing f, Monad f, CharParsing f) => f NLength-newickLength = do- maybeLength <- optional ((symbolic ':') >> double)- pure maybeLength---- Branch → Subtree Length-newickBranch :: (TokenParsing f, Monad f, CharParsing f) => f NBranch-newickBranch = do- st <- newickSubtree- l <- newickLength- pure (NBranch st l)---- BranchSet → Branch | Branch "," BranchSet-newickBranchSet :: (TokenParsing f, Monad f, CharParsing f) => f NBranchSet-newickBranchSet = do- bs <- sepBy1 newickBranch comma- pure (NBranchSet bs)---- Internal → "(" BranchSet ")" Name-newickInternal :: (TokenParsing f, Monad f, CharParsing f) => f NSubtree-newickInternal = do- bs <- parens newickBranchSet- pure (NInternal bs)---- Subtree → Leaf | Internal-newickSubtree :: (TokenParsing f, Monad f, CharParsing f) => f NSubtree-newickSubtree = choice [newickInternal,newickLeaf]---- Tree → Subtree ";" | Branch ";"-newickTree :: (TokenParsing f, Monad f, CharParsing f) => f NTree-newickTree = do- (NBranchSet bs) <- parens newickBranchSet- symbolic ';'- pure (NTree bs)- -- | Example run -- > (Success foo) = parseString newickTree mempty "((foo:1.1,bar:1.2):1.3,baz:1.4);" -- > (Success bar) = parseString newickTree mempty $ show foo -- > foo == bar -- True- sort :: Ord a => [a] -> [a] sort = List.sort count' :: (a -> Bool) -> [a] -> Int count' p = go 0- where go n [] = n- go n (x:xs) | p x = go (n+1) xs- | otherwise = go n xs+ where+ go n [] = n+ go n (x:xs)+ | p x = go (n + 1) xs+ | otherwise = go n xs --- | Run a simulation described by a configuration object.-simulation :: (ModelParameters a)- => Bool -- ^ Condition upon at least two leaves in the reconstructed tree- -> SimulationConfiguration a b- -> (a -> Time -> (Time, [EpidemicEvent], b, Integer) -> GenIO -> IO (Time, [EpidemicEvent], b, Integer))- -> IO [EpidemicEvent]-simulation True config allEvents = do- gen <- System.Random.MWC.create :: IO GenIO- simulation' config allEvents gen-simulation False SimulationConfiguration {..} allEvents = do- gen <- System.Random.MWC.create :: IO GenIO- (_, events, _, _) <-- allEvents rates timeLimit (0, [], population, newIdentifier) gen- return $ sort events+-- | Run a simulation described by a configuration object with the provided+-- PRNG.+simulationWithGenIO ::+ (ModelParameters a b, Population b)+ => SimulationConfiguration a b+ -> (a -> AbsoluteTime -> Maybe (b -> Bool) -> SimulationState b -> GenIO -> IO (SimulationState b))+ -> GenIO+ -> IO [EpidemicEvent]+simulationWithGenIO config@SimulationConfiguration {..} allEventsFunc gen =+ if scRequireCherry+ then do+ simulation' config allEventsFunc gen+ else do+ SimulationState (_, events, _, _) <-+ allEventsFunc+ scRates+ (timeAfterDelta scStartTime scSimDuration)+ scValidPopulation+ (SimulationState (AbsoluteTime 0, [], scPopulation, scNewIdentifier))+ gen+ return $ sort events --- | Predicate for whether an epidemic event is either an occurrence or a--- disaaster.-isNonReconTreeObservation :: EpidemicEvent -> Bool-isNonReconTreeObservation e = case e of- Occurrence {} -> True- Disaster {} -> True- _ -> False+-- | Run a simulation described by a configuration object using the fixed PRNG+-- that is hardcoded in the @mwc-random@ package.+simulation ::+ (ModelParameters a b, Population b)+ => SimulationConfiguration a b+ -> (a -> AbsoluteTime -> Maybe (b -> Bool) -> SimulationState b -> GenIO -> IO (SimulationState b))+ -> IO [EpidemicEvent]+simulation config allEventsFunc = do+ gen <- System.Random.MWC.create :: IO GenIO+ simulationWithGenIO config allEventsFunc gen -- | Predicate for whether an epidemic event will appear as a leaf in the -- reconstructed tree. isReconTreeLeaf :: EpidemicEvent -> Bool-isReconTreeLeaf e = case e of- Sampling {} -> True- Catastrophe {} -> True- _ -> False-+isReconTreeLeaf e =+ case e of+ IndividualSample {..} -> indSampSeq+ PopulationSample {..} -> popSampSeq+ _ -> False -simulation' :: (ModelParameters a) => SimulationConfiguration a b- -> (a -> Time -> (Time, [EpidemicEvent], b, Integer) -> GenIO -> IO (Time, [EpidemicEvent], b, Integer))- -> GenIO- -> IO [EpidemicEvent]-simulation' config@SimulationConfiguration {..} allEvents gen = do- (_, events, _, _) <-- allEvents rates timeLimit (0, [], population, newIdentifier) gen+-- | Simulation conditioned upon there being at least two sequenced samples.+-- NOTE This function is deprecated and will be removed in future versions.+simulation' ::+ (ModelParameters a b, Population b)+ => SimulationConfiguration a b+ -> (a -> AbsoluteTime -> Maybe (b -> Bool) -> SimulationState b -> GenIO -> IO (SimulationState b))+ -> GenIO+ -> IO [EpidemicEvent]+simulation' config@SimulationConfiguration {..} allEventsFunc gen = do+ SimulationState (_, events, _, _) <-+ allEventsFunc+ scRates+ (timeAfterDelta scStartTime scSimDuration)+ scValidPopulation+ (SimulationState (AbsoluteTime 0, [], scPopulation, scNewIdentifier))+ gen if count' isReconTreeLeaf events >= 2 then return $ sort events- else simulation' config allEvents gen-+ else simulation' config allEventsFunc gen -- | Run a simulation described by a configuration object but using a random -- seed generated by the system rather than a seed-simulationWithSystemRandom :: (ModelParameters a)- => Bool -- ^ Condition upon at least two leaves in the reconstructed tree- -> SimulationConfiguration a b- -> (a -> Time -> (Time, [EpidemicEvent], b, Integer) -> GenIO -> IO (Time, [EpidemicEvent], b, Integer))- -> IO [EpidemicEvent]-simulationWithSystemRandom atLeastCherry config@SimulationConfiguration {..} allEvents = do- (_, events, _, _) <-+simulationWithSystemRandom ::+ (ModelParameters a b, Population b)+ => SimulationConfiguration a b+ -> (a -> AbsoluteTime -> Maybe (b -> Bool) -> SimulationState b -> GenIO -> IO (SimulationState b))+ -> IO [EpidemicEvent]+simulationWithSystemRandom config@SimulationConfiguration {..} allEventsFunc = do+ SimulationState (_, events, _, _) <- withSystemRandom $ \g ->- allEvents rates timeLimit (0, [], population, newIdentifier) g- if atLeastCherry+ allEventsFunc+ scRates+ (timeAfterDelta scStartTime scSimDuration)+ scValidPopulation+ (SimulationState (AbsoluteTime 0, [], scPopulation, scNewIdentifier))+ g+ if scRequireCherry then (if count' isReconTreeLeaf events >= 2- then return $ sort events- else simulationWithSystemRandom True config allEvents)+ then return $ sort events+ else simulationWithSystemRandom config allEventsFunc) else return $ sort events - -- | The number of lineages at the end of a simulation.-finalSize :: [EpidemicEvent] -- ^ The events from the simulation- -> Integer+finalSize ::+ [EpidemicEvent] -- ^ The events from the simulation+ -> Integer finalSize = foldl (\x y -> x + eventPopDelta y) 1 --- | Generate exponentially distributed random variates with inhomogeneous rate.-inhomExponential :: PrimMonad m- => Timed Double -- ^ Step function- -> Gen (PrimState m) -- ^ Generator.- -> m Double-inhomExponential stepFunc gen = do- maybeVariate <- randInhomExp 0 stepFunc gen- if Maybe.isJust maybeVariate- then return $ Maybe.fromJust maybeVariate- else inhomExponential stepFunc gen+-- | 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.-randInhomExp :: PrimMonad m- => Double -- ^ Timer- -> Timed Double -- ^ Step function- -> Gen (PrimState m) -- ^ Generator.- -> m (Maybe Double)+--+-- __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)+ in if Maybe.isJust crrR && Maybe.isJust nxtT then do crrD <- exponential (Maybe.fromJust crrR) gen- if crrT + crrD < (Maybe.fromJust nxtT)- then return $ Just (crrD + crrT)- else (randInhomExp (Maybe.fromJust nxtT) stepFunc 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
test/Spec.hs view
@@ -5,16 +5,16 @@ import qualified Data.Aeson as Json import qualified Data.ByteString as B import qualified Data.ByteString.Builder as BBuilder-import Data.Csv+import Data.Either (isRight) import Data.Maybe (fromJust, isJust, isNothing) import qualified Data.Vector as V import Epidemic-import qualified Epidemic.BDSCOD as BDSCOD-import qualified Epidemic.BirthDeath as BD-import qualified Epidemic.BirthDeathSamplingCatastropheOccurrence as BDSCO-import qualified Epidemic.BirthDeathSamplingOccurrence as BDSO-import qualified Epidemic.InhomogeneousBDS as InhomBDS+import qualified Epidemic.Model.BDSCOD as BDSCOD+import qualified Epidemic.Model.InhomogeneousBDS as InhomBDS import Epidemic.Types.Events+import Epidemic.Types.Observations+import Epidemic.Types.Time+import Epidemic.Types.Newick import Epidemic.Types.Parameter import Epidemic.Types.Population import Epidemic.Utility@@ -22,195 +22,241 @@ import qualified System.Random.MWC as MWC import Test.Hspec +-- | Helper function for converting from Either to Maybe monad.+either2Maybe x = case x of+ Right v -> Just v+ Left _ -> Nothing+ -- | y is within n% of x from x. withinNPercent n x y = x - d < y && y < x + d where d = n * x / 100 -p1 = Person 1+p1 = Person (Identifier 1) -p2 = Person 2+p2 = Person (Identifier 2) -p3 = Person 3+p3 = Person (Identifier 3) -p4 = Person 4+p4 = Person (Identifier 4) -p5 = Person 5+p5 = Person (Identifier 5) -p6 = Person 6+p6 = Person (Identifier 6) -p7 = Person 7+p7 = Person (Identifier 7) -- | The first set of test data does not have any catastrophe events. demoFullEvents01 =- [ Infection 1 p1 p2- , Infection 2 p1 p3- , Sampling 3 p1- , Infection 4 p2 p4- , Infection 5 p2 p5- , Sampling 6 p4- , Infection 7 p3 p6- , Occurrence 8 p2- , Removal 9 p3- , Infection 10 p5 p7- , Occurrence 11 p6- , Sampling 12 p5- , Removal 13 p7+ [ Infection (AbsoluteTime 1) p1 p2+ , Infection (AbsoluteTime 2) p1 p3+ , IndividualSample (AbsoluteTime 3) p1 True+ , Infection (AbsoluteTime 4) p2 p4+ , Infection (AbsoluteTime 5) p2 p5+ , IndividualSample (AbsoluteTime 6) p4 True+ , Infection (AbsoluteTime 7) p3 p6+ , IndividualSample (AbsoluteTime 8) p2 False+ , Removal (AbsoluteTime 9) p3+ , Infection (AbsoluteTime 10) p5 p7+ , IndividualSample (AbsoluteTime 11) p6 False+ , IndividualSample (AbsoluteTime 12) p5 True+ , Removal (AbsoluteTime 13) p7 ] demoSampleEvents01 =- [ Infection 1 p1 p2- , Sampling 3 p1- , Infection 4 p2 p4- , Sampling 6 p4- , Occurrence 8 p2- , Occurrence 11 p6- , Sampling 12 p5+ [ Infection (AbsoluteTime 1) p1 p2+ , IndividualSample (AbsoluteTime 3) p1 True+ , Infection (AbsoluteTime 4) p2 p4+ , IndividualSample (AbsoluteTime 6) p4 True+ , IndividualSample (AbsoluteTime 8) p2 False+ , IndividualSample (AbsoluteTime 11) p6 False+ , IndividualSample (AbsoluteTime 12) p5 True ] -- | The second set of test data is the same as the first but includes a -- catastrophe event. demoFullEvents02 =- [ Infection 1 p1 p2- , Infection 2 p1 p3- , Sampling 3 p1- , Infection 4 p2 p4- , Infection 5 p2 p5- , Sampling 6 p4- , Infection 7 p3 p6- , Occurrence 8 p2- , Removal 9 p3- , Infection 10 p5 p7- , Catastrophe 11 (asPeople [p5])- , Occurrence 12 p6- , Removal 13 p7+ [ Infection (AbsoluteTime 1) p1 p2+ , Infection (AbsoluteTime 2) p1 p3+ , IndividualSample (AbsoluteTime 3) p1 True+ , Infection (AbsoluteTime 4) p2 p4+ , Infection (AbsoluteTime 5) p2 p5+ , IndividualSample (AbsoluteTime 6) p4 True+ , Infection (AbsoluteTime 7) p3 p6+ , IndividualSample (AbsoluteTime 8) p2 False+ , Removal (AbsoluteTime 9) p3+ , Infection (AbsoluteTime 10) p5 p7+ , PopulationSample (AbsoluteTime 11) (asPeople [p5]) True+ , IndividualSample (AbsoluteTime 12) p6 False+ , Removal (AbsoluteTime 13) p7 ] demoSampleEvents02 =- [ Infection 1 p1 p2- , Sampling 3 p1- , Infection 4 p2 p4- , Sampling 6 p4- , Occurrence 8 p2- , Catastrophe 11 (asPeople [p5])- , Occurrence 12 p6+ [ Infection (AbsoluteTime 1) p1 p2+ , IndividualSample (AbsoluteTime 3) p1 True+ , Infection (AbsoluteTime 4) p2 p4+ , IndividualSample (AbsoluteTime 6) p4 True+ , IndividualSample (AbsoluteTime 8) p2 False+ , PopulationSample (AbsoluteTime 11) (asPeople [p5]) True+ , IndividualSample (AbsoluteTime 12) p6 False ] -- | Another test set to test that catastrophes are handled correctly. demoFullEvents03 =- [ Infection 1 p1 p4- , Infection 2 p1 p2- , Sampling 3 p1- , Infection 4 p2 p3- , Infection 5 p4 p5- , Catastrophe 6 (asPeople [p2, p3, p4])+ [ Infection (AbsoluteTime 1) p1 p4+ , Infection (AbsoluteTime 2) p1 p2+ , IndividualSample (AbsoluteTime 3) p1 True+ , Infection (AbsoluteTime 4) p2 p3+ , Infection (AbsoluteTime 5) p4 p5+ , PopulationSample (AbsoluteTime 6) (asPeople [p2, p3, p4]) True ] demoSampleEvents03 =- [ Infection 1 p1 p4- , Infection 2 p1 p2- , Sampling 3 p1- , Infection 4 p2 p3- , Catastrophe 6 (asPeople [p2, p3, p4])+ [ Infection (AbsoluteTime 1) p1 p4+ , Infection (AbsoluteTime 2) p1 p2+ , IndividualSample (AbsoluteTime 3) p1 True+ , Infection (AbsoluteTime 4) p2 p3+ , PopulationSample (AbsoluteTime 6) (asPeople [p2, p3, p4]) True ] -- | Another test to make sure that disasters are handled. demoFullEvents04 =- [ Infection 1 p1 p4- , Infection 2 p1 p2- , Sampling 3 p1- , Infection 4 p2 p3- , Infection 5 p4 p5- , Catastrophe 6 (asPeople [p2, p3, p4])- , Infection 7 p5 p6- , Infection 8 p5 p7- , Disaster 9 (asPeople [p5, p6])+ [ Infection (AbsoluteTime 1) p1 p4+ , Infection (AbsoluteTime 2) p1 p2+ , IndividualSample (AbsoluteTime 3) p1 True+ , Infection (AbsoluteTime 4) p2 p3+ , Infection (AbsoluteTime 5) p4 p5+ , PopulationSample (AbsoluteTime 6) (asPeople [p2, p3, p4]) True+ , Infection (AbsoluteTime 7) p5 p6+ , Infection (AbsoluteTime 8) p5 p7+ , PopulationSample (AbsoluteTime 9) (asPeople [p5, p6]) False ] demoSampleEvents04 =- [ Infection 1 p1 p4- , Infection 2 p1 p2- , Sampling 3 p1- , Infection 4 p2 p3- , Catastrophe 6 (asPeople [p2, p3, p4])- , Disaster 9 (asPeople [p5, p6])+ [ Infection (AbsoluteTime 1) p1 p4+ , Infection (AbsoluteTime 2) p1 p2+ , IndividualSample (AbsoluteTime 3) p1 True+ , Infection (AbsoluteTime 4) p2 p3+ , PopulationSample (AbsoluteTime 6) (asPeople [p2, p3, p4]) True+ , PopulationSample (AbsoluteTime 9) (asPeople [p5, p6]) False ] eventHandlingTests = do describe "Post-simulation processing" $ do it "Extracting observed events" $ do- (demoSampleEvents01 == BDSO.observedEvents demoFullEvents01) `shouldBe`+ let demoEvents =+ [ PopulationSample (AbsoluteTime 0.5) (asPeople []) True -- Because the first event is a null event it can be ignored!+ , Infection (AbsoluteTime 1.0) p1 p2+ , PopulationSample (AbsoluteTime 1.5) (asPeople []) True+ , PopulationSample (AbsoluteTime 2.0) (asPeople [p1, p2]) True+ ]+ (length demoEvents == 4) `shouldBe` True+ ((length <$> observedEvents (tail demoEvents)) == (Right 2)) `shouldBe` True- (demoSampleEvents02 == BDSO.observedEvents demoFullEvents02) `shouldBe`+ ((length <$> observedEvents (demoEvents)) == (Right 2)) `shouldBe` True+ (observedEvents (demoEvents) == observedEvents (tail demoEvents)) `shouldBe` True- let demoEvents = [Catastrophe 0.5 (asPeople []) -- Because the first event is a null event it can be ignored!- ,Infection 1.0 p1 p2- ,Catastrophe 1.5 (asPeople [])- ,Catastrophe 2.0 (asPeople [p1,p2])]- (length demoEvents == 4) `shouldBe` True- ((length <$> BDSCOD.observedEvents (tail demoEvents)) == (Just 2)) `shouldBe` True- ((length <$> BDSCOD.observedEvents (demoEvents)) == (Just 2)) `shouldBe` True- (BDSCOD.observedEvents (demoEvents) == BDSCOD.observedEvents (tail demoEvents)) `shouldBe` True- (maybeEpidemicTree (demoEvents) == maybeEpidemicTree (tail demoEvents)) `shouldBe` True+ (maybeEpidemicTree (demoEvents) == maybeEpidemicTree (tail demoEvents)) `shouldBe`+ True describe "Catastrophe definitions" $ do it "Check we can find a catastrophe" $ do- (noScheduledEvent 0 1 (Timed [])) `shouldBe` True- (noScheduledEvent 0 1 (Timed [(2, 0.5)])) `shouldBe` True- (noScheduledEvent 0 1 (Timed [(0.5, 0.5)])) `shouldBe` False- (noScheduledEvent 0 1 (Timed [(2, 0.6), (0.5, 0.5)])) `shouldBe` False+ (noScheduledEvent (AbsoluteTime 0) (AbsoluteTime 1) (Timed [])) `shouldBe`+ True+ (noScheduledEvent+ (AbsoluteTime 0)+ (AbsoluteTime 1)+ (Timed [(AbsoluteTime 2, 0.5)])) `shouldBe`+ True+ (noScheduledEvent+ (AbsoluteTime 0)+ (AbsoluteTime 1)+ (Timed [(AbsoluteTime 0.5, 0.5)])) `shouldBe`+ False+ (noScheduledEvent+ (AbsoluteTime 0)+ (AbsoluteTime 1)+ (Timed [(AbsoluteTime 2, 0.6), (AbsoluteTime 0.5, 0.5)])) `shouldBe`+ False it "Check we can find a particular catastrophe" $ do- (firstScheduled 1 (Timed [])) `shouldBe` Nothing- (firstScheduled 1 (Timed [(2, 0.5)])) `shouldBe` Just (2, 0.5)- (firstScheduled 1 (Timed [(0.5, 0.5)])) `shouldBe` Nothing- (firstScheduled 1 (Timed [(2, 0.6), (0.5, 0.5)])) `shouldBe` Just (2, 0.6)- isNothing (asTimed [(2, 0.6 :: Rate), (0.5, 0.5), (1.5, 0.4)]) `shouldBe` True- (firstScheduled 1 (Timed [(2, 0.6), (0.5, 0.5), (1.5, 0.4)])) `shouldBe`- Just (2, 0.6)- it "Works on a very specific case it seems to not like" $ do- (noScheduledEvent 2.28 (2.28 + 0.42) (Timed [(2.3, 0.9)])) `shouldBe` False- it "Catastrophes are handled correctly" $ do- (demoSampleEvents03 == BDSCO.observedEvents demoFullEvents03) `shouldBe`+ (firstScheduled (AbsoluteTime 1) (Timed [])) `shouldBe` Nothing+ (firstScheduled (AbsoluteTime 1) (Timed [(AbsoluteTime 2, 0.5)])) `shouldBe`+ Just (AbsoluteTime 2, 0.5)+ (firstScheduled (AbsoluteTime 1) (Timed [(AbsoluteTime 0.5, 0.5)])) `shouldBe`+ Nothing+ (firstScheduled+ (AbsoluteTime 1)+ (Timed [(AbsoluteTime 2, 0.6), (AbsoluteTime 0.5, 0.5)])) `shouldBe`+ Just (AbsoluteTime 2, 0.6)+ isNothing+ (asTimed+ [ (AbsoluteTime 2, 0.6 :: Rate)+ , (AbsoluteTime 0.5, 0.5)+ , (AbsoluteTime 1.5, 0.4)+ ]) `shouldBe` True- it "Catastrophes can be simulated" $ do- demoSim <-- simulation False- (fromJust (BDSCO.configuration 4 (1.3, 0.1, 0.1, ([(3, 0.5)]), 0.2)))- BDSCO.allEvents- length demoSim > 1 `shouldBe` True+ (firstScheduled+ (AbsoluteTime 1)+ (Timed+ [ (AbsoluteTime 2, 0.6)+ , (AbsoluteTime 0.5, 0.5)+ , (AbsoluteTime 1.5, 0.4)+ ])) `shouldBe`+ Just (AbsoluteTime 2, 0.6)+ it "Works on a very specific case it seems to not like" $ do+ (noScheduledEvent+ (AbsoluteTime 2.28)+ (AbsoluteTime (2.28 + 0.42))+ (Timed [(AbsoluteTime 2.3, 0.9)])) `shouldBe`+ False describe "Disaster definitions" $ do it "Disasters are handled correctly" $ do- (demoSampleEvents04 == fromJust (BDSCOD.observedEvents demoFullEvents04)) `shouldBe`+ ((Right $ [Observation e | e <- demoSampleEvents04]) ==+ (observedEvents demoFullEvents04)) `shouldBe` True it "Disasters can be simulated" $ do demoSim <-- simulation False- (fromJust (BDSCOD.configuration 4 (1.3, 0.1, 0.1, [(3, 0.5)], 0.2, [(3.5, 0.5)])))- BDSCOD.allEvents+ simulation+ (fromJust+ (BDSCOD.configuration+ (TimeDelta 4)+ False+ ( 1.3+ , 0.1+ , 0.1+ , [(AbsoluteTime 3, 0.5)]+ , 0.2+ , [(AbsoluteTime 3.5, 0.5)])))+ (allEvents BDSCOD.randomEvent) length demoSim > 1 `shouldBe` True--birthDeathTests = do- describe "BirthDeath module tests" $ do- it "Construct a simulation configuration" $ do- (isJust (BD.configuration 1 (1, 1))) `shouldBe` True- (isJust (BD.configuration (-1) (1, 1))) `shouldBe` False- (isJust (BD.configuration 1 ((-1), 1))) `shouldBe` False- (isJust (BD.configuration 1 (1, (-1)))) `shouldBe` False- (isJust (BD.configuration 1 ((-1), (-1)))) `shouldBe` False- it "Mean behaviour is approximately correct" $- let mean xs = fromIntegral (sum xs) / (fromIntegral $ length xs)- meanFinalSize = exp ((2.1 - 0.2) * 1.5)- randomBDEvents =- simulationWithSystemRandom False- (fromJust $ BD.configuration 1.5 (2.1, 0.2))- BD.allEvents- numRepeats = 3000- in do finalSizes <- replicateM numRepeats (finalSize <$> randomBDEvents)- (withinNPercent 5 (mean finalSizes) meanFinalSize) `shouldBe` True+ describe "Extracting observed events" $ do+ it "unseq obs still extracted if no seq obs" $ do+ let noSequencedEvents =+ [ Infection (AbsoluteTime 4.1) p1 p2+ , Infection (AbsoluteTime 4.3) p2 p3+ , Infection (AbsoluteTime 4.5) p2 p4+ , IndividualSample+ { indSampTime = AbsoluteTime 5.3+ , indSampPerson = p1+ , indSampSeq = False+ }+ , StoppingTime+ ]+ expectedObs =+ [ Observation+ (IndividualSample+ { indSampTime = AbsoluteTime 5.3+ , indSampPerson = p1+ , indSampSeq = False+ })+ ]+ isRight (observedEvents noSequencedEvents) `shouldBe` True+ ((Right expectedObs) == (observedEvents noSequencedEvents)) `shouldBe`+ True helperFuncTests = do describe "Helpers in Utility" $ do it "the isAscending function works" $ do- (isAscending ([] :: [Time])) `shouldBe` True+ (isAscending ([] :: [AbsoluteTime])) `shouldBe` True (isAscending [-1.0]) `shouldBe` True (isAscending [1.0]) `shouldBe` True (isAscending [1.0, 2.0]) `shouldBe` True@@ -220,151 +266,160 @@ (isAscending [1.0, 2.0, -3.0]) `shouldBe` False it "the asTimed function works" $ do (isJust $ asTimed []) `shouldBe` True- (isJust $ asTimed [(0, 1)]) `shouldBe` True- (isJust $ asTimed [(0, 1), (1, 3)]) `shouldBe` True- (isJust $ asTimed [(0, 3), (1, 1)]) `shouldBe` True- (isJust $ asTimed [(1, 3), (0, 1)]) `shouldBe` False- let demoTimed = fromJust $ asTimed [(0, 1.2), (1, 3.1), (2, 2.7)]+ (isJust $ asTimed [(AbsoluteTime 0, 1)]) `shouldBe` True+ (isJust $ asTimed [(AbsoluteTime 0, 1), (AbsoluteTime 1, 3)]) `shouldBe`+ True+ (isJust $ asTimed [(AbsoluteTime 0, 3), (AbsoluteTime 1, 1)]) `shouldBe`+ True+ (isJust $ asTimed [(AbsoluteTime 1, 3), (AbsoluteTime 0, 1)]) `shouldBe`+ False+ let demoTimed =+ fromJust $+ asTimed+ [ (AbsoluteTime 0, 1.2)+ , (AbsoluteTime 1, 3.1)+ , (AbsoluteTime 2, 2.7)+ ] in do it "the cadlagValue function works" $ do- (isJust $ cadlagValue demoTimed (-1.0)) `shouldBe` False- ((== 1.2) . fromJust $ cadlagValue demoTimed 0.0) `shouldBe` True- ((== 1.2) . fromJust $ cadlagValue demoTimed 0.5) `shouldBe` True- ((== 3.1) . fromJust $ cadlagValue demoTimed 1.5) `shouldBe` True+ (isJust $ cadlagValue demoTimed (AbsoluteTime (-1.0))) `shouldBe`+ False+ ((== 1.2) . fromJust $ cadlagValue demoTimed (AbsoluteTime 0.0)) `shouldBe`+ True+ ((== 1.2) . fromJust $ cadlagValue demoTimed (AbsoluteTime 0.5)) `shouldBe`+ True+ ((== 3.1) . fromJust $ cadlagValue demoTimed (AbsoluteTime 1.5)) `shouldBe`+ True it "the diracDeltaValue function works" $ do- ((== 1.2) . fromJust $ diracDeltaValue demoTimed 0) `shouldBe` True- (isJust $ diracDeltaValue demoTimed 1) `shouldBe` True- (isJust $ diracDeltaValue demoTimed 0.9) `shouldBe` False- (isJust $ diracDeltaValue demoTimed 1.1) `shouldBe` False+ ((== 1.2) . fromJust $ diracDeltaValue demoTimed (AbsoluteTime 0)) `shouldBe`+ True+ (isJust $ diracDeltaValue demoTimed (AbsoluteTime 1)) `shouldBe`+ True+ (isJust $ diracDeltaValue demoTimed (AbsoluteTime 0.9)) `shouldBe`+ False+ (isJust $ diracDeltaValue demoTimed (AbsoluteTime 1.1)) `shouldBe`+ False it "the hasTime function works" $ do- (hasTime demoTimed 0) `shouldBe` True- (hasTime demoTimed 0.5) `shouldBe` False- (hasTime demoTimed 1) `shouldBe` True- (hasTime demoTimed 1.5) `shouldBe` False+ (hasTime demoTimed (AbsoluteTime 0)) `shouldBe` True+ (hasTime demoTimed (AbsoluteTime 0.5)) `shouldBe` False+ (hasTime demoTimed (AbsoluteTime 1)) `shouldBe` True+ (hasTime demoTimed (AbsoluteTime 1.5)) `shouldBe` False it "the nextTime function works" $ do- (0 == (fromJust $ nextTime demoTimed (-1))) `shouldBe` True- (1 == (fromJust $ nextTime demoTimed (0))) `shouldBe` True- (1 == (fromJust $ nextTime demoTimed (0.5))) `shouldBe` True+ (AbsoluteTime 0 ==+ (fromJust $ nextTime demoTimed (AbsoluteTime (-1)))) `shouldBe`+ True+ (AbsoluteTime 1 == (fromJust $ nextTime demoTimed (AbsoluteTime 0))) `shouldBe`+ True+ (AbsoluteTime 1 ==+ (fromJust $ nextTime demoTimed (AbsoluteTime 0.5))) `shouldBe`+ True it "the nextTime function handles the last time correctly" $ do- isJust (nextTime demoTimed 1.9) `shouldBe` True- isJust (nextTime demoTimed 2.0) `shouldBe` True- isJust (nextTime demoTimed 2.1) `shouldBe` True- isJust (nextTime demoTimed 10.0) `shouldBe` True+ isJust (nextTime demoTimed (AbsoluteTime 1.9)) `shouldBe` True+ isJust (nextTime demoTimed (AbsoluteTime 2.0)) `shouldBe` True+ isJust (nextTime demoTimed (AbsoluteTime 2.1)) `shouldBe` True+ isJust (nextTime demoTimed (AbsoluteTime 10.0)) `shouldBe` True it "shifted times work" $- let sf = fromJust $ asTimed [(-1.0,2.0),(1,3.0)]- val1 = cadlagValue sf 0- val2 = cadlagValue sf (-2.0)- val3 = cadlagValue sf 1.5- in do- isJust val1 `shouldBe` True- val1 == Just 2.0 `shouldBe` True- (not $ isJust val2) `shouldBe` True- isJust val3 `shouldBe` True- val3 == Just 3.0 `shouldBe` True+ let sf =+ fromJust $+ asTimed [(AbsoluteTime (-1.0), 2.0), (AbsoluteTime 1, 3.0)]+ val1 = cadlagValue sf (AbsoluteTime 0)+ val2 = cadlagValue sf (AbsoluteTime (-2.0))+ val3 = cadlagValue sf (AbsoluteTime 1.5)+ in do isJust val1 `shouldBe` True+ val1 == Just 2.0 `shouldBe` True+ (not $ isJust val2) `shouldBe` True+ isJust val3 `shouldBe` True+ val3 == Just 3.0 `shouldBe` True it "the asTimed function returns nothing as expected" $ do- (isJust $ asTimed [(0.0,-1)]) `shouldBe` True- (isJust $ asTimed [(0.0,1),(1.0,-1)]) `shouldBe` True- (isJust $ InhomBDS.inhomBDSRates [(0.0,1),(1.0,-1)] 0.5 0.5) `shouldBe` False----readwriteTests =- do- describe "Change Event read/write" $ do- it "check we can writte an event" $- let demoPerson = Person 3- demoPersonField = toField demoPerson- demoPersonField' = "3"- demoEvent = Removal 1.0 demoPerson- demoRecord = toRecord demoEvent- demoRecord' = V.fromList ["removal", "1.0", "3", "NA"] :: Record- (Right demoEvent') =- runParser (parseRecord demoRecord) :: Either String EpidemicEvent- demoRecord2 =- toRecord (Catastrophe 1.0 (asPeople [p2, p3]))- (Right demoEvent2@(Catastrophe _ people2)) =- runParser (parseRecord demoRecord2) :: Either String EpidemicEvent- demoRecord2' = toRecord demoEvent2- in do (demoPersonField' == demoPersonField) `shouldBe` True- (demoRecord' == demoRecord) `shouldBe` True- (demoEvent' == demoEvent) `shouldBe` True- (demoRecord2' == demoRecord2) `shouldBe` True- (numPeople people2 == 2) `shouldBe` True-+ (isJust $ asTimed [(AbsoluteTime 0.0, -1)]) `shouldBe` True+ (isJust $ asTimed [(AbsoluteTime 0.0, 1), (AbsoluteTime 1.0, -1)]) `shouldBe`+ True+ let (Just timedBirthRate) =+ asTimed [(AbsoluteTime 0.0, 1.0), (AbsoluteTime 1.0, -1.0)]+ (isJust $ InhomBDS.inhomBDSRates timedBirthRate 0.5 0.5) `shouldBe` False inhomExpTests = describe "Test the inhomogeneous exponential variate generator" $ let rate1 = 2.0- sF1 = fromJust $ asTimed [(0, rate1)]+ sF1 = fromJust $ asTimed [(AbsoluteTime 0, rate1)] mean1 = 1 / rate1 var1 = 1 / (rate1 ** 2.0)- sF2 = fromJust $ asTimed [(0, 1e-10),(1, rate1)]+ sF2 =+ fromJust $ asTimed [(AbsoluteTime 0, 1e-10), (AbsoluteTime 1, rate1)] mean2 = 1 / rate1 + 1 var2 = var1 genAction = MWC.createSystemRandom- in do it "check we can get a positive variate out" $- do- gen <- genAction- u1 <- MWC.uniform gen :: IO Double- (u1 > 0) `shouldBe` True- x1 <- inhomExponential sF1 gen- (x1 > 0) `shouldBe` True- (x1 < 100) `shouldBe` True- True `shouldBe` True- it "check the mean and variance look sensible" $- do gen <- genAction- x <- V.replicateM 20000 (inhomExponential sF1 gen)- withinNPercent 5 (mean x) mean1 `shouldBe` True- withinNPercent 5 (variance x) var1 `shouldBe` True- it "check the mean and variance look sensible with delay" $- do gen <- genAction- x <- V.replicateM 20000 (inhomExponential sF2 gen)- withinNPercent 5 (mean x) mean2 `shouldBe` True- withinNPercent 5 (variance x) var2 `shouldBe` True-+ in do it "check we can get a positive variate out" $ do+ gen <- genAction+ u1 <- MWC.uniform gen :: IO Double+ (u1 > 0) `shouldBe` True+ (Just x1) <- inhomExponential sF1 (AbsoluteTime 0) gen+ (x1 > AbsoluteTime 0) `shouldBe` True+ (x1 < AbsoluteTime 100) `shouldBe` True+ True `shouldBe` True+ it "check the mean and variance look sensible" $ do+ gen <- genAction+ xBoxed <-+ V.replicateM 20000 (inhomExponential sF1 (AbsoluteTime 0) gen)+ let x = fmap (\(Just (AbsoluteTime t)) -> t) xBoxed+ withinNPercent 5 (mean x) mean1 `shouldBe` True+ withinNPercent 5 (variance x) var1 `shouldBe` True+ it "check the mean and variance look sensible with delay" $ do+ gen <- genAction+ xBoxed <-+ V.replicateM 20000 (inhomExponential sF2 (AbsoluteTime 0) gen)+ let x = fmap (\(Just (AbsoluteTime t)) -> t) xBoxed+ withinNPercent 5 (mean x) mean2 `shouldBe` True+ withinNPercent 5 (variance x) var2 `shouldBe` True +illFormedTreeTest :: SpecWith () illFormedTreeTest = describe "Prevent the simulator returning a broken tree" $ do- let simDuration = 0.2- simLambda = 3.2- simMu = 0.3- simPsi = 0.3- simRho = 0.15- simRhoTime = 2.6- simOmega = 0.3- simNu = 0.15- simNuTime = 3.0- simParams = (simLambda, simMu, simPsi, [(simRhoTime,simRho)], simOmega, [(simNuTime,simNu)])- simConfig = BDSCOD.configuration simDuration simParams- in it "stress testing the observed events function" $- do- null (BDSCOD.observedEvents []) `shouldBe` True- simEvents <- simulation True (fromJust simConfig) BDSCOD.allEvents- any isReconTreeLeaf simEvents `shouldBe` True- (length (fromJust $ BDSCOD.observedEvents simEvents) > 1) `shouldBe` True----+ let simDuration = TimeDelta 0.2+ simLambda = 3.2+ simMu = 0.3+ simPsi = 0.3+ simRho = 0.15+ simRhoTime = AbsoluteTime 2.6+ simOmega = 0.3+ simNu = 0.15+ simNuTime = AbsoluteTime 3.0+ simParams =+ ( simLambda+ , simMu+ , simPsi+ , [(simRhoTime, simRho)]+ , simOmega+ , [(simNuTime, simNu)])+ simConfig = BDSCOD.configuration simDuration True simParams+ in it "stress testing the observed events function" $ do+ null (observedEvents []) `shouldBe` True+ simEvents <-+ simulation (fromJust simConfig) (allEvents BDSCOD.randomEvent)+ any isReconTreeLeaf simEvents `shouldBe` True+ let (Right oes) = observedEvents simEvents+ (length oes > 1) `shouldBe` True inhomogeneousBDSTest = describe "InhomogeneousBDS module tests" $ do it "Check the observedEvents filters out removals" $- let demoAllEvents = [Infection 0.1 p1 p2- ,Sampling 0.2 p1- ,Removal 0.3 p3- ,Sampling 0.4 p2]- demoObsEvents = [Infection 0.1 p1 p2- ,Sampling 0.2 p1- ,Sampling 0.4 p2]- compObsEvents = InhomBDS.observedEvents demoAllEvents- in do- (compObsEvents == demoObsEvents) `shouldBe` True-+ let demoAllEvents =+ [ Infection (AbsoluteTime 0.1) p1 p2+ , IndividualSample (AbsoluteTime 0.2) p1 True+ , Removal (AbsoluteTime 0.3) p3+ , IndividualSample (AbsoluteTime 0.4) p2 True+ ]+ demoObsEvents =+ [ Infection (AbsoluteTime 0.1) p1 p2+ , IndividualSample (AbsoluteTime 0.2) p1 True+ , IndividualSample (AbsoluteTime 0.4) p2 True+ ]+ compObsEvents = observedEvents demoAllEvents+ in do (compObsEvents == (Right [Observation e | e <- demoObsEvents])) `shouldBe` True helperTypeTests = do describe "Helpers for working with the types" $ do it "the isAscending function works" $ do- (isAscending ([] :: [Time])) `shouldBe` True+ (isAscending ([] :: [AbsoluteTime])) `shouldBe` True (isAscending [-1.0]) `shouldBe` True (isAscending [1.0]) `shouldBe` True (isAscending [1.0, 2.0]) `shouldBe` True@@ -374,35 +429,59 @@ (isAscending [1.0, 2.0, -3.0]) `shouldBe` False it "the asTimed function works" $ do (isJust $ asTimed []) `shouldBe` True- (isJust $ asTimed [(0, 1)]) `shouldBe` True- (isJust $ asTimed [(0, 1), (1, 3)]) `shouldBe` True- (isJust $ asTimed [(0, 3), (1, 1)]) `shouldBe` True- (isJust $ asTimed [(1, 3), (0, 1)]) `shouldBe` False- let demoTimed = fromJust $ asTimed [(0, 1.2), (1, 3.1), (2, 2.7)]+ (isJust $ asTimed [(AbsoluteTime 0, 1)]) `shouldBe` True+ (isJust $ asTimed [(AbsoluteTime 0, 1), (AbsoluteTime 1, 3)]) `shouldBe`+ True+ (isJust $ asTimed [(AbsoluteTime 0, 3), (AbsoluteTime 1, 1)]) `shouldBe`+ True+ (isJust $ asTimed [(AbsoluteTime 1, 3), (AbsoluteTime 0, 1)]) `shouldBe`+ False+ let demoTimed =+ fromJust $+ asTimed+ [ (AbsoluteTime 0, 1.2)+ , (AbsoluteTime 1, 3.1)+ , (AbsoluteTime 2, 2.7)+ ] in do it "the cadlagValue function works" $ do- (isJust $ cadlagValue demoTimed (-1.0)) `shouldBe` False- ((== 1.2) . fromJust $ cadlagValue demoTimed 0.0) `shouldBe` True- ((== 1.2) . fromJust $ cadlagValue demoTimed 0.5) `shouldBe` True- ((== 3.1) . fromJust $ cadlagValue demoTimed 1.5) `shouldBe` True+ (isJust $ cadlagValue demoTimed (AbsoluteTime (-1.0))) `shouldBe`+ False+ ((== 1.2) . fromJust $ cadlagValue demoTimed (AbsoluteTime 0.0)) `shouldBe`+ True+ ((== 1.2) . fromJust $ cadlagValue demoTimed (AbsoluteTime 0.5)) `shouldBe`+ True+ ((== 3.1) . fromJust $ cadlagValue demoTimed (AbsoluteTime 1.5)) `shouldBe`+ True it "the diracDeltaValue function works" $ do- ((== 1.2) . fromJust $ diracDeltaValue demoTimed 0) `shouldBe` True- (isJust $ diracDeltaValue demoTimed 1) `shouldBe` True- (isJust $ diracDeltaValue demoTimed 0.9) `shouldBe` False- (isJust $ diracDeltaValue demoTimed 1.1) `shouldBe` False+ ((== 1.2) . fromJust $ diracDeltaValue demoTimed (AbsoluteTime 0)) `shouldBe`+ True+ (isJust $ diracDeltaValue demoTimed (AbsoluteTime 1)) `shouldBe`+ True+ (isJust $ diracDeltaValue demoTimed (AbsoluteTime 0.9)) `shouldBe`+ False+ (isJust $ diracDeltaValue demoTimed (AbsoluteTime 1.1)) `shouldBe`+ False it "the hasTime function works" $ do- (hasTime demoTimed 0) `shouldBe` True- (hasTime demoTimed 0.5) `shouldBe` False- (hasTime demoTimed 1) `shouldBe` True- (hasTime demoTimed 1.5) `shouldBe` False+ (hasTime demoTimed (AbsoluteTime 0)) `shouldBe` True+ (hasTime demoTimed (AbsoluteTime 0.5)) `shouldBe` False+ (hasTime demoTimed (AbsoluteTime 1)) `shouldBe` True+ (hasTime demoTimed (AbsoluteTime 1.5)) `shouldBe` False it "the nextTime function works" $ do- (0 == (fromJust $ nextTime demoTimed (-1))) `shouldBe` True- (1 == (fromJust $ nextTime demoTimed (0))) `shouldBe` True- (1 == (fromJust $ nextTime demoTimed (0.5))) `shouldBe` True+ (AbsoluteTime 0 ==+ (fromJust $ nextTime demoTimed (AbsoluteTime (-1)))) `shouldBe`+ True+ (AbsoluteTime 1 == (fromJust $ nextTime demoTimed (AbsoluteTime 0))) `shouldBe`+ True+ (AbsoluteTime 1 ==+ (fromJust $ nextTime demoTimed (AbsoluteTime 0.5))) `shouldBe`+ True it "shifted times work" $- let sf = fromJust $ asTimed [(-1.0, 2.0), (1, 3.0)]- val1 = cadlagValue sf 0- val2 = cadlagValue sf (-2.0)- val3 = cadlagValue sf 1.5+ let sf =+ fromJust $+ asTimed [(AbsoluteTime (-1.0), 2.0), (AbsoluteTime 1, 3.0)]+ val1 = cadlagValue sf (AbsoluteTime 0)+ val2 = cadlagValue sf (AbsoluteTime (-2.0))+ val3 = cadlagValue sf (AbsoluteTime 1.5) in do isJust val1 `shouldBe` True val1 == Just 2.0 `shouldBe` True (not $ isJust val2) `shouldBe` True@@ -412,94 +491,259 @@ jsonTests = do describe "Converting to and from JSON" $ do it "Conversion of Timed Rate" $ do- let demoObj = Timed [(0.0, 1.0), (1.0, 1.0)] :: Timed Rate+ let demoObj =+ Timed [(AbsoluteTime 0.0, 1.0), (AbsoluteTime 1.0, 1.0)] :: Timed Rate (Timed demoVals) = demoObj demoJson = "[[0,1],[1,1]]" encodedObj = Json.encode demoObj decodedJson = Json.decode demoJson :: Maybe (Timed Rate) in do True `shouldBe` True- let (Timed foo) = demoObj in demoVals == foo `shouldBe` True+ let (Timed foo) = demoObj+ in demoVals == foo `shouldBe` True encodedObj == demoJson `shouldBe` True isJust decodedJson `shouldBe` True- let (Timed bar) = fromJust decodedJson in demoVals == bar `shouldBe` True+ let (Timed bar) = fromJust decodedJson+ in demoVals == bar `shouldBe` True equalBuilders :: BBuilder.Builder -> BBuilder.Builder -> Bool equalBuilders a b = BBuilder.toLazyByteString a == BBuilder.toLazyByteString b - newickTests =- let p1 = Person 1- p2 = Person 2- p3 = Person 3- ps = asPeople [p1,p2]- maybeEpiTree = maybeEpidemicTree [Infection 1 p1 p2,Infection 2 p2 p3,Catastrophe 3 (asPeople [p1,p3]),Removal 4 p2]- maybeEpiTree' = maybeEpidemicTree [Infection 1 p1 p2,Infection 2 p2 p3,Catastrophe 3 (asPeople [p1,p3]),Sampling 4 p2]- maybeEpiTree'' = maybeEpidemicTree [Infection 1 p1 p2,Infection 2 p2 p3,Disaster 3 (asPeople [p1,p3]),Sampling 4 p2]- in- describe "Writing to Newick" $ do- it "equalBuilders works as expected" $ do- equalBuilders (BBuilder.charUtf8 ':') (BBuilder.charUtf8 ':') `shouldBe` True- equalBuilders (BBuilder.charUtf8 'a') (BBuilder.charUtf8 ':') `shouldBe` False- it "derivedFrom works as expected" $ do- let p1 = Person 1- let p2 = Person 2- let p3 = Person 3- let e = [Infection 0.3 p1 p2]- derivedFrom p1 e == derivedFrom p2 e `shouldBe` True- derivedFrom p1 e /= derivedFrom p3 e `shouldBe` True- derivedFrom p1 e /= [] `shouldBe` True- null (derivedFrom p3 e) `shouldBe` True- derivedFrom p1 e == e `shouldBe` True- let foo = derivedFrom (Person 1) [Infection 0.3 (Person 1) (Person 2),Sampling 0.7 (Person 1)]- let bar = derivedFrom (Person 2) [Infection 0.3 (Person 1) (Person 2),Sampling 0.7 (Person 1)]- foo == bar `shouldBe` True- it "maybeEpidemicTree works as expected: 1" $ do- let e1 = Removal 1 (Person 1)- maybeEpidemicTree [e1] == Just (Leaf e1) `shouldBe` True- let t1 = maybeEpidemicTree [Infection 0.3 (Person 1) (Person 2),Sampling 0.6 (Person 2),Sampling 0.7 (Person 1)]- let t2 = Just (Branch (Infection 0.3 (Person 1) (Person 2)) (Leaf (Sampling 0.7 (Person 1))) (Leaf (Sampling 0.6 (Person 2))))- t1 == t2 `shouldBe` True- maybeEpidemicTree [Infection 0.3 (Person 1) (Person 2)] == Just (Branch (Infection 0.3 (Person 1) (Person 2)) (Shoot (Person 1)) (Shoot (Person 2))) `shouldBe` True- maybeEpidemicTree [Infection 0.3 (Person 1) (Person 2),Sampling 0.7 (Person 1)] == Just (Branch (Infection 0.3 (Person 1) (Person 2)) (Leaf (Sampling 0.7 (Person 1))) (Shoot (Person 2))) `shouldBe` True- let trickyEvents = [Infection 0.3 (Person 1) (Person 2),Infection 0.4 (Person 2) (Person 3),Sampling 0.6 (Person 3),Sampling 0.7 (Person 1)]- isJust (maybeEpidemicTree trickyEvents) `shouldBe` True- it "maybeEpidemicTree works as expected: 2" $ do- let p1 = Person 1- p2 = Person 2- demoEvents = [Catastrophe 0.5 (asPeople []) -- Because the first event is a null event it can be ignored!- ,Infection 1.0 p1 p2- ,Catastrophe 1.5 (asPeople [])- ,Catastrophe 2.0 (asPeople [p1,p2])]- (length demoEvents == 4) `shouldBe` True- (maybeEpidemicTree demoEvents == maybeEpidemicTree (tail demoEvents)) `shouldBe` True- it "asNewickString works for EpidemicTree" $ do- let trickyEvents = [Infection 0.3 (Person 1) (Person 2),Infection 0.4 (Person 2) (Person 3),Sampling 0.6 (Person 3),Sampling 0.7 (Person 1)]- let maybeNewickPair = asNewickString (0, Person 1) =<< maybeEpidemicTree trickyEvents- let newickTarget = BBuilder.stringUtf8 "(1:0.39999999999999997,(2:Infinity,3:0.19999999999999996):0.10000000000000003):0.3"- let maybeReconTree = maybeReconstructedTree =<< maybeEpidemicTree trickyEvents- isJust maybeNewickPair `shouldBe` True- [Sampling 0.6 (Person 3),Sampling 0.7 (Person 1)] == snd (fromJust maybeNewickPair) `shouldBe` True- equalBuilders newickTarget (fst $ fromJust maybeNewickPair) `shouldBe` True- isJust maybeReconTree `shouldBe` True- it "asNewickString works for ReconstructedTree" $ do- isJust (asNewickString (0,Person 1) (RLeaf (Sampling 1 (Person 1)))) `shouldBe` True- let trickyEvents = [Infection 0.3 (Person 1) (Person 2),Infection 0.4 (Person 2) (Person 3),Sampling 0.6 (Person 3),Sampling 0.7 (Person 1)]- let maybeNewickPair = asNewickString (0, Person 1) =<< maybeReconstructedTree =<< maybeEpidemicTree trickyEvents- let newickTarget = BBuilder.stringUtf8 "(1:0.39999999999999997,3:0.3):0.3"- isJust maybeNewickPair `shouldBe` True- [Sampling 0.6 (Person 3),Sampling 0.7 (Person 1)] == snd (fromJust maybeNewickPair) `shouldBe` True- equalBuilders newickTarget (fst $ fromJust maybeNewickPair) `shouldBe` True- let catasNewick = (asNewickString (0,Person 1) (RLeaf (Catastrophe 1 (asPeople [Person 1,Person 2]))))- let catasTarget = BBuilder.stringUtf8 "1&2:1.0"- equalBuilders catasTarget (fst $ fromJust catasNewick) `shouldBe` True+ let p1 = Person (Identifier 1)+ p2 = Person (Identifier 2)+ p3 = Person (Identifier 3)+ ps = asPeople [p1, p2]+ maybeEpiTree =+ maybeEpidemicTree+ [ Infection (AbsoluteTime 1) p1 p2+ , Infection (AbsoluteTime 2) p2 p3+ , PopulationSample (AbsoluteTime 3) (asPeople [p1, p3]) True+ , Removal (AbsoluteTime 4) p2+ ]+ maybeEpiTree' =+ maybeEpidemicTree+ [ Infection (AbsoluteTime 1) p1 p2+ , Infection (AbsoluteTime 2) p2 p3+ , PopulationSample (AbsoluteTime 3) (asPeople [p1, p3]) True+ , IndividualSample (AbsoluteTime 4) p2 True+ ]+ maybeEpiTree'' =+ maybeEpidemicTree+ [ Infection (AbsoluteTime 1) p1 p2+ , Infection (AbsoluteTime 2) p2 p3+ , PopulationSample (AbsoluteTime 3) (asPeople [p1, p3]) False+ , IndividualSample (AbsoluteTime 4) p2 True+ ]+ in describe "Writing to Newick" $ do+ it "equalBuilders works as expected" $ do+ equalBuilders (BBuilder.charUtf8 ':') (BBuilder.charUtf8 ':') `shouldBe`+ True+ equalBuilders (BBuilder.charUtf8 'a') (BBuilder.charUtf8 ':') `shouldBe`+ False+ it "derivedFrom works as expected" $ do+ let p1 = Person (Identifier 1)+ let p2 = Person (Identifier 2)+ let p3 = Person (Identifier 3)+ let e = [Infection (AbsoluteTime 0.3) p1 p2]+ derivedFrom p1 e == derivedFrom p2 e `shouldBe` True+ derivedFrom p1 e /= derivedFrom p3 e `shouldBe` True+ derivedFrom p1 e /= [] `shouldBe` True+ null (derivedFrom p3 e) `shouldBe` True+ derivedFrom p1 e == e `shouldBe` True+ let foo =+ derivedFrom+ (Person (Identifier 1))+ [ Infection+ (AbsoluteTime 0.3)+ (Person (Identifier 1))+ (Person (Identifier 2))+ , IndividualSample+ (AbsoluteTime 0.7)+ (Person (Identifier 1))+ True+ ]+ let bar =+ derivedFrom+ (Person (Identifier 2))+ [ Infection+ (AbsoluteTime 0.3)+ (Person (Identifier 1))+ (Person (Identifier 2))+ , IndividualSample+ (AbsoluteTime 0.7)+ (Person (Identifier 1))+ True+ ]+ foo == bar `shouldBe` True+ it "maybeEpidemicTree works as expected: 1" $ do+ let e1 = Removal (AbsoluteTime 1) (Person (Identifier 1))+ maybeEpidemicTree [e1] == Right (Leaf e1) `shouldBe` True+ let t1 =+ maybeEpidemicTree+ [ Infection+ (AbsoluteTime 0.3)+ (Person (Identifier 1))+ (Person (Identifier 2))+ , IndividualSample+ (AbsoluteTime 0.6)+ (Person (Identifier 2))+ True+ , IndividualSample+ (AbsoluteTime 0.7)+ (Person (Identifier 1))+ True+ ]+ let t2 =+ Right+ (Branch+ (Infection+ (AbsoluteTime 0.3)+ (Person (Identifier 1))+ (Person (Identifier 2)))+ (Leaf+ (IndividualSample+ (AbsoluteTime 0.7)+ (Person (Identifier 1))+ True))+ (Leaf+ (IndividualSample+ (AbsoluteTime 0.6)+ (Person (Identifier 2))+ True)))+ t1 == t2 `shouldBe` True+ maybeEpidemicTree+ [ Infection+ (AbsoluteTime 0.3)+ (Person (Identifier 1))+ (Person (Identifier 2))+ ] ==+ Right+ (Branch+ (Infection+ (AbsoluteTime 0.3)+ (Person (Identifier 1))+ (Person (Identifier 2)))+ (Shoot (Person (Identifier 1)))+ (Shoot (Person (Identifier 2)))) `shouldBe`+ True+ maybeEpidemicTree+ [ Infection+ (AbsoluteTime 0.3)+ (Person (Identifier 1))+ (Person (Identifier 2))+ , IndividualSample (AbsoluteTime 0.7) (Person (Identifier 1)) True+ ] ==+ Right+ (Branch+ (Infection+ (AbsoluteTime 0.3)+ (Person (Identifier 1))+ (Person (Identifier 2)))+ (Leaf+ (IndividualSample+ (AbsoluteTime 0.7)+ (Person (Identifier 1))+ True))+ (Shoot (Person (Identifier 2)))) `shouldBe`+ True+ let trickyEvents =+ [ Infection+ (AbsoluteTime 0.3)+ (Person (Identifier 1))+ (Person (Identifier 2))+ , Infection+ (AbsoluteTime 0.4)+ (Person (Identifier 2))+ (Person (Identifier 3))+ , IndividualSample+ (AbsoluteTime 0.6)+ (Person (Identifier 3))+ True+ , IndividualSample+ (AbsoluteTime 0.7)+ (Person (Identifier 1))+ True+ ]+ isRight (maybeEpidemicTree trickyEvents) `shouldBe` True+ it "maybeEpidemicTree works as expected: 2" $ do+ let p1 = Person (Identifier 1)+ p2 = Person (Identifier 2)+ demoEvents =+ [ PopulationSample (AbsoluteTime 0.5) (asPeople []) True -- Because the first event is a null event it can be ignored!+ , Infection (AbsoluteTime 1.0) p1 p2+ , PopulationSample (AbsoluteTime 1.5) (asPeople []) True+ , PopulationSample (AbsoluteTime 2.0) (asPeople [p1, p2]) True+ ]+ (length demoEvents == 4) `shouldBe` True+ (maybeEpidemicTree demoEvents == maybeEpidemicTree (tail demoEvents)) `shouldBe`+ True+ -- it "asNewickString works for EpidemicTree" $ do+ -- let trickyEvents = [+ -- Infection (AbsoluteTime 0.3) (Person (Identifier 1)) (Person (Identifier 2)),+ -- Infection (AbsoluteTime 0.4) (Person (Identifier 2)) (Person (Identifier 3)),+ -- IndividualSample (AbsoluteTime 0.6) (Person (Identifier 3)) True,+ -- IndividualSample (AbsoluteTime 0.7) (Person (Identifier 1)) True]+ -- let maybeNewickPair = asNewickString (AbsoluteTime 0, Person (Identifier 1)) =<< maybeEpidemicTree trickyEvents+ -- let newickTarget = BBuilder.stringUtf8 "(1:0.39999999999999997,(2:Infinity,3:0.19999999999999996):0.10000000000000003):0.3"+ -- let maybeReconTree = maybeReconstructedTree =<< maybeEpidemicTree trickyEvents+ -- isJust maybeNewickPair `shouldBe` True+ -- [IndividualSample (AbsoluteTime 0.6) (Person (Identifier 3)) True, IndividualSample (AbsoluteTime 0.7) (Person (Identifier 1)) True] == snd (fromJust maybeNewickPair) `shouldBe` True+ -- equalBuilders newickTarget (fst $ fromJust maybeNewickPair) `shouldBe` True+ -- isJust maybeReconTree `shouldBe` True+ it "asNewickString works for ReconstructedTree" $ do+ isJust+ (asNewickString+ (AbsoluteTime 0, Person (Identifier 1))+ (RLeaf+ (Observation (IndividualSample+ (AbsoluteTime 1)+ (Person (Identifier 1))+ True)))) `shouldBe`+ True+ let trickyEvents =+ [ Infection+ (AbsoluteTime 0.3)+ (Person (Identifier 1))+ (Person (Identifier 2))+ , Infection+ (AbsoluteTime 0.4)+ (Person (Identifier 2))+ (Person (Identifier 3))+ , IndividualSample+ (AbsoluteTime 0.6)+ (Person (Identifier 3))+ True+ , IndividualSample+ (AbsoluteTime 0.7)+ (Person (Identifier 1))+ True+ ]+ let et = maybeEpidemicTree trickyEvents :: Either String EpidemicTree+ rt = maybeReconstructedTree =<< et :: Either String ReconstructedTree+ maybeNewickPair = asNewickString (AbsoluteTime 0, Person (Identifier 1)) =<< (either2Maybe rt)+ let newickTarget =+ BBuilder.stringUtf8 "(1:0.39999999999999997,3:0.3):0.3"+ isJust maybeNewickPair `shouldBe` True+ [ IndividualSample (AbsoluteTime 0.6) (Person (Identifier 3)) True, IndividualSample (AbsoluteTime 0.7) (Person (Identifier 1)) True] == snd (fromJust maybeNewickPair) `shouldBe` True+ equalBuilders newickTarget (fst $ fromJust maybeNewickPair) `shouldBe`+ True+ let catasNewick =+ (asNewickString+ (AbsoluteTime 0, Person (Identifier 1))+ (RLeaf+ (Observation (PopulationSample+ (AbsoluteTime 1)+ (asPeople+ [Person (Identifier 1), Person (Identifier 2)])+ True))))+ let catasTarget = BBuilder.stringUtf8 "1&2:1.0"+ equalBuilders catasTarget (fst $ fromJust catasNewick) `shouldBe` True main :: IO () main = hspec $ do eventHandlingTests- birthDeathTests helperFuncTests- readwriteTests inhomExpTests illFormedTreeTest inhomogeneousBDSTest