prob-fx-0.1.0.2: examples/SIRNonModular.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators, TypeApplications #-}
{-# LANGUAGE OverloadedLabels #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Redundant return" #-}
{- |
This demonstrates:
- The [SIR](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology) model for modelling
the transition between Susceptible (S), Infected (I), and Recovered (R) individuals during an epidemic.
We model this as a Hidden Markov Model, where the _latent states_ are the true values of S, I, and R,
and the _observations_ are the reported number of infections (π).
- Extending the SIR to the SIRS model where recovered individuals (R) can become susceptible (S) again.
- Extending the SIRS to the SIRSV model where susceptible individuals (S) can become vaccinated (V).
Note that the extensions (SIRS and SIRSV) aren't as modular as we would like, due to having to
redefine the data types Popl and TransParams when adding new variables to the SIR model.
The file [SIRModular](examples/SIR.hs) shows how one could take steps to resolve this by using
extensible records.
-}
module SIRNonModular where
import Prog ( Member )
import Effects.Writer ( Writer, tellM, handleWriterM )
import Model ( Model, beta, binomial', gamma, poisson )
import Inference.SIM as SIM ( simulate )
import Inference.MH as MH ( mh )
import Sampler ( Sampler )
import Env ( Observables, Observable(get), Assign((:=)), nil, (<:>) )
import Control.Monad ( (>=>) )
import HMM ( ObsModel, TransModel, hmmGen )
{- | SIR model.
-}
-- | SIR model environment
type SIRenv =
'[ "Ξ²" := Double -- ^ mean contact rate between susceptible and infected people
, "Ξ³" := Double -- ^ mean recovery rate
, "Ο" := Double -- ^ mean report rate of infection
, "π" := Int -- ^ number of reported infections
]
-- | Latent state
data Popl = Popl {
s :: Int, -- ^ number of people susceptible to infection
i :: Int, -- ^ number of people currently infected
r :: Int -- ^ number of people recovered from infection
} deriving Show
-- | Transition model parameters
data TransParamsSIR = TransParamsSIR {
betaP :: Double, -- ^ mean contact rate between susceptible and infected people
gammaP :: Double -- ^ mean recovery rate
}
-- | Observation π
type Reported = Int
-- | Observation model parameters
type ObsParams = Double
-- | Transition model prior
transPriorSIR :: Observables env '["Ξ²", "Ξ³"] Double
=> Model env ts TransParamsSIR
transPriorSIR = do
pBeta <- gamma 2 1 #Ξ²
pGamma <- gamma 1 (1/8) #Ξ³
return (TransParamsSIR pBeta pGamma)
-- | Transition model between S and I
transSI :: TransModel env ts Double Popl
transSI beta (Popl s i r) = do
let pop = s + i + r
dN_SI <- binomial' s (1 - exp ((-beta * fromIntegral i) / fromIntegral pop))
return $ Popl (s - dN_SI) (i + dN_SI) r
-- | Transition model between I and R
transIR :: TransModel env ts Double Popl
transIR gamma (Popl s i r) = do
dN_IR <- binomial' i (1 - exp (-gamma))
return $ Popl s (i - dN_IR) (r + dN_IR)
-- | Transition model between S, I, and R
transSIR :: Member (Writer [Popl]) es
=> TransModel env es TransParamsSIR Popl
transSIR (TransParamsSIR beta gamma) sir = do
sir' <- (transSI beta >=> transIR gamma) sir
tellM [sir'] -- a user effect for writing each latent SIR state to a stream [Popl]
return sir'
-- | Observation model prior
obsPriorSIR :: Observables env '["Ο"] Double
=> Model env ts ObsParams
obsPriorSIR = do
pRho <- beta 2 7 #Ο
return pRho
-- | Observation model from I to π
obsSIR :: Observable env "π" Int
=> ObsModel env ts Double Popl Reported
obsSIR rho (Popl _ i _) = do
i <- poisson (rho * fromIntegral i) #π
return i
-- | SIR as HMM
hmmSIR :: (Member (Writer [Popl]) es, Observable env "π" Int, Observables env '["Ο", "Ξ²", "Ξ³"] Double)
=> Int -> Popl -> Model env es Popl
hmmSIR = hmmGen transPriorSIR obsPriorSIR transSIR obsSIR
-- | Handle the user effect for writing each SIR state to a stream [Popl]
hmmSIR' :: (Observables env '["π"] Int , Observables env '[ "Ξ²" , "Ξ³" , "Ο"] Double) => Int -> Popl -> Model env es (Popl, [Popl])
hmmSIR' n = handleWriterM . hmmSIR n
-- | Simulating from SIR model: ([(s, i, r)], [π])
simulateSIR :: Sampler ([(Int, Int, Int)], [Reported])
simulateSIR = do
-- Specify model input of 762 susceptible and 1 infected
let sir_0 = Popl {s = 762, i = 1, r = 0}
-- Specify model environment
sim_env_in = #Ξ² := [0.7] <:> #Ξ³ := [0.009] <:> #Ο := [0.3] <:> #π := [] <:> nil
-- Simulate an epidemic over 100 days
((_, sir_trace), sim_env_out) <- SIM.simulate (hmmSIR' 100) sim_env_in sir_0
-- Get the observed infections over 100 days
let πs :: [Reported] = get #π sim_env_out
-- Get the true SIR values over 100 days
sirs = map (\(Popl s i recov) -> (s, i, recov)) sir_trace
return (sirs, πs)
-- | MH inference from SIR model: ([Ο], [Ξ²])
inferSIR :: Sampler ([Double], [Double])
inferSIR = do
-- Simulate some observed infections
πs <- snd <$> simulateSIR
-- Specify model input of 762 susceptible and 1 infected
let sir_0 = Popl {s = 762, i = 1, r = 0}
-- Specify model environment
mh_env_in = #Ξ² := [] <:> #Ξ³ := [0.0085] <:> #Ο := [] <:> #π := πs <:> nil
-- Run MH inference over 50000 iterations
mhTrace <- MH.mh 5000 (hmmSIR' 100) (sir_0, mh_env_in) ["Ξ²", "Ο"]
-- Get the sampled values for model parameters Ο and Ξ²
let Οs = concatMap (get #Ο) mhTrace
Ξ²s = concatMap (get #Ξ²) mhTrace
return (Οs, Ξ²s)
{- | SIRS model.
-}
-- | Transition model parameters
data TransParamsSIRS = TransParamsSIRS {
betaP_SIRS :: Double, -- ^ mean contact rate between susceptible and infected people
gammaP_SIRS :: Double, -- ^ mean recovery rate
etaP_SIRS :: Double -- ^ rate of resusceptible
}
-- | Transition model prior
transPriorSIRS :: Observables env '["Ξ²", "Ξ·", "Ξ³"] Double
=> Model env ts TransParamsSIRS
transPriorSIRS = do
TransParamsSIR pBeta pGamma <- transPriorSIR
pEta <- gamma 1 (1/8) #Ξ·
return (TransParamsSIRS pBeta pGamma pEta)
-- | Transition model between R and S
transRS :: Double -> Popl -> Model env ts Popl
transRS eta (Popl s i r) = do
dN_RS <- binomial' r (1 - exp (-eta))
return $ Popl (s + dN_RS) i (r - dN_RS)
-- | Transition model between S, to I, to R, and to S
transSIRS :: Member (Writer [Popl]) es
=> TransModel env es TransParamsSIRS Popl
transSIRS (TransParamsSIRS beta gamma eta) sir = do
sir' <- (transSI beta >=> transIR gamma >=> transRS eta) sir
tellM [sir']
return sir'
-- | SIRS as HMM
hmmSIRS :: (Observables env '["π"] Int, Observables env '["Ξ²", "Ξ·", "Ξ³", "Ο"] Double) => Int -> Popl -> Model env ts (Popl, [Popl])
hmmSIRS n = handleWriterM . hmmGen transPriorSIRS obsPriorSIR transSIRS obsSIR n
-- | Simulate from SIRS model: ([(s, i, r)], [π])
simulateSIRS :: Sampler ([(Int, Int, Int)], [Reported])
simulateSIRS = do
-- Specify model input of 762 susceptible and 1 infected
let sir_0 = Popl {s = 762, i = 1, r = 0}
-- Specify model environment
sim_env_in = #Ξ² := [0.7] <:> #Ξ³ := [0.009] <:> #Ξ· := [0.05] <:> #Ο := [0.3] <:> #π := [] <:> nil
-- Simulate an epidemic over 100 days
((_, sir_trace), sim_env_out) <- SIM.simulate (hmmSIRS 100) sim_env_in sir_0
-- Get the observed infections over 100 days
let πs :: [Reported] = get #π sim_env_out
-- Get the true SIR values over 100 days
sirs = map (\(Popl s i recov) -> (s, i, recov)) sir_trace
return (sirs, πs)
{- | SIRSV model.
-}
-- | Transition model parameters
data TransParamsSIRSV = TransParamsSIRSV {
betaP_SIRSV :: Double, -- ^ mean contact rate between susceptible and infected people
gammaP_SIRSV :: Double, -- ^ mean recovery rate
etaP_SIRSV :: Double, -- ^ rate of resusceptible
omegaP_SIRSV :: Double -- ^ vaccination rate
}
-- | Latent state
data PoplV = PoplV {
s' :: Int, -- ^ susceptible individuals
i' :: Int, -- ^ infected individuals
r' :: Int, -- ^ recovered individuals
v' :: Int -- ^ vaccinated individuals
} deriving Show
-- | Transition from S to I
transSI' :: TransModel env ts Double PoplV
transSI' beta (PoplV s i r v) = do
let pop = s + i + r + v
dN_SI <- binomial' s (1 - exp ((-beta * fromIntegral i) / fromIntegral pop))
return $ PoplV (s - dN_SI) (i + dN_SI) r v
-- | Transition from I to R
transIR' :: TransModel env ts Double PoplV
transIR' gamma (PoplV s i r v) = do
dN_IR <- binomial' i (1 - exp (-gamma))
return $ PoplV s (i - dN_IR) (r + dN_IR) v
-- | Transition from R to S
transRS' :: TransModel env es Double PoplV
transRS' eta (PoplV s i r v) = do
dN_RS <- binomial' r (1 - exp (-eta))
return $ PoplV (s + dN_RS) i (r - dN_RS) v
-- | Transition from S to V
transSV' :: TransModel env es Double PoplV
transSV' omega (PoplV s i r v) = do
dN_SV <- binomial' s (1 - exp (-omega))
return $ PoplV (s - dN_SV) i r (v + dN_SV )
-- | Transition between S to I, I to R, R to S, and S to V
transSIRSV :: Member (Writer [PoplV]) ts => TransModel env ts TransParamsSIRSV PoplV
transSIRSV (TransParamsSIRSV beta gamma omega eta) sirv = do
sirv' <- (transSI' beta >=>
transIR' gamma >=>
transRS' eta >=>
transSV' omega) sirv
tellM [sirv']
return sirv'
-- | Transition model prior
transPriorSIRSV :: Observables env '["Ξ²", "Ξ³", "Ο", "Ξ·"] Double
=> Model env ts TransParamsSIRSV
transPriorSIRSV = do
TransParamsSIRS pBeta pGamma pEta <- transPriorSIRS
pOmega <- gamma 1 (1/16) #Ο
return (TransParamsSIRSV pBeta pGamma pEta pOmega)
-- | Observation model
obsSIRSV :: Observable env "π" Int
=> ObsModel env ts Double PoplV Reported
obsSIRSV rho (PoplV _ i _ v) = do
i <- poisson (rho * fromIntegral i) #π
return i
-- | SIRSV as HMM
hmmSIRSV :: (Observables env '["π"] Int, Observables env '["Ξ²", "Ξ³", "Ξ·", "Ο", "Ο"] Double) => Int -> PoplV -> Model env ts (PoplV, [PoplV])
hmmSIRSV n = handleWriterM . hmmGen transPriorSIRSV obsPriorSIR transSIRSV obsSIRSV n
-- | Simulate from SIRSV model : ([(s, i, r, v)], [π])
simulateSIRSV :: Sampler ([(Int, Int, Int, Int)], [Reported])
simulateSIRSV = do
-- Specify model input of 762 susceptible and 1 infected
let sirv_0 = PoplV {s' = 762, i' = 1, r' = 0, v' = 0}
-- Specify model environment
sim_env_in = #Ξ² := [0.7] <:> #Ξ³ := [0.009] <:> #Ξ· := [0.05] <:> #Ο := [0.02] <:> #Ο := [0.3] <:> #π := [] <:> nil
-- Simulate an epidemic over 100 days
((_, sirv_trace), sim_env_out) <- SIM.simulate (hmmSIRSV 100) sim_env_in sirv_0
-- Get the observed infections over 100 days
let πs :: [Reported] = get #π sim_env_out
-- Get the true SIRV values over 100 days
sirvs = map (\(PoplV s i recov v) -> (s, i, recov, v)) sirv_trace
return (sirvs, πs)