packages feed

epi-sim-0.7.0: src/Epidemic/Types/Simulation.hs

{-# LANGUAGE GADTs #-}

module Epidemic.Types.Simulation
  ( SimulationConfiguration(..)
  , SimulationState(..)
  , SimulationRandEvent(..)
  , TerminationHandler(..)
  , genIOFromFixed
  , genIOFromWord32
  , genIOFromSystem
  ) where

import qualified Data.Vector.Unboxed       as Unboxed
import           Epidemic.Types.Events
import           Epidemic.Types.Parameter
import           Epidemic.Types.Population
import           Epidemic.Types.Time       (AbsoluteTime (..), TimeDelta (..))
import           GHC.Word                  (Word32)
import           System.Random.MWC         (GenIO, create, createSystemRandom,
                                            initialize)

data SimulationConfiguration r p s =
  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
    , scTerminationHandler :: Maybe (TerminationHandler p s)
      -- | The simulation requires at least two sequenced samples
    , scRequireCherry   :: Bool
    }

-- | Either there is a valid simulation state which contains a sequence of
-- epidemic events along with the time and population or, if the simulation has
-- terminated early there is another value to indicate that along with a value
-- which can be used to indicate why the simulation was terminated early.
data SimulationState b c
  = SimulationState (AbsoluteTime, [EpidemicEvent], b, Identifier)
  | TerminatedSimulation (Maybe c)
  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

-- | Check if a simulation should be terminated and if it should be terminated,
-- then compute a summary explaining why. The first function is used to
-- determine whether the population has entered a state which requires the
-- simulation to terminate early and the second can be use to write a summary of
-- the events that led to the termination.
data TerminationHandler b c where
  TerminationHandler
    :: Population b
    => (b -> Bool)
    -> ([EpidemicEvent] -> c)
    -> TerminationHandler b c

-- | A PRNG seed based on the given number. This is the best choice for
-- reproducible simulations.
genIOFromWord32 :: Word32 -> IO GenIO
genIOFromWord32 seed = initialize (Unboxed.fromList [seed])

-- | A PRNG seed generated by the system's random number generator.
genIOFromSystem :: IO GenIO
genIOFromSystem = createSystemRandom

-- | A PRNG seed which is hard coded into @mwc-random@.
genIOFromFixed :: IO GenIO
genIOFromFixed = create