library(diffeqr)
<- diffeqr::diffeq_setup() de
SEIR model with varying number of compartments
The \(SEIR\) model structures each stage as a distinct compartment, with transitions between compartments dictated by specific rate constants, except for the transition between \(S\) and \(E\) that involve force of infection which is also a function of \(t\). Waiting times in the \(E\) and \(I\) compartments are assumed to follow an exponential distribution. However, to better reflect reality, it might be necessary to consider alternative distributions. For instance, the waiting time in the \(E\) compartment, i.e., incubation period, could be represented by a lognormal or Weibull distribution. Within the framework of ordinary differential equations (ODEs), incorporating these distributions directly poses challenges. Nevertheless, by dividing a single compartment into multiple sub-compartments, waiting times follow the Erlang distribution, effectively capturing the essence of both lognormal and Weibull distributions.
The process of adding multiple sub-compartments is relatively straightforward. The challenge arises when one wishes to dynamically adjust the number of sub-compartments within a single model framework. This post discusses a method to achieve such adaptability in model construction. To enhance model flexibility, I extensively employ strings, specifically utilizing the eval(str2lang("some text"))
function.
diffeqr
package
diffeqr
package is used use Julia’s DifferentialEquations
library
First, let’s construct the classic SEIR model for comparison. This model is equivalent to a variant with variable compartments for the \(I\) stage when considering only a single compartment.
<- function(u, p, t){
seir
<- u[1];
S <- u[2];
E # the number of compartments to model the duration of infectiousness
<- u[3];
I <- u[4];
R <- u[5];
C
# population size
<- S+E+I+R
pop
<- p[1] # 1/latent period
epsilon <- p[2] # 1/duration of infectiousness
gamma <- p[3] # transmission rate
beta <- p[4] # 1/omega = duration of natural immunity
omega # force of infection
<- beta*I/pop
foi
<- epsilon
muEI <- gamma
muIR <- omega
muRS
# differential equations
<- - foi*S + muRS*R
dS <- foi*S - muEI*E
dE <- muEI*E - muIR*I
dI <- muIR*I - muRS*R
dR
<- muEI*E
dC
return(c(dS,dE,dI,dR,dC))
}
# simulation
<- 2 # basic reproduction number
R0 <- 1/4 # 1/epsilon = incubation period
epsilon <- 1/7 # 1/gamma = duration of infectiousness
gamma <- R0*gamma # instantaneous transmission rate
beta <- 1/(4*365) # natural immunity waning rate
omega # parameters
<- c(epsilon=epsilon, gamma=gamma, beta=beta, omega=omega)
params <- c(0.99, 0, 0.01, 0, 0)
u0 <- 100 #
tend <- c(0.0, tend)
tspan
<- de$ODEProblem(seir, u0, tspan, params)
prob <- de$solve(prob, de$Tsit5(), saveat=1)
sol <- sapply(sol$u, identity)
mat <- as.data.frame(t(mat))
udf <- cbind(data.frame(t=sol$t), udf)
out names(out) <- c("t","S","E","I","R","C")
\(SEIR\) model with str2lang
The model with variable compartments for the \(I\) stage. A part of the model is written using strings using paste
, which are later evaluated using eval(str2lang("some string"))
.
<- function(u, p, t){
seir_nstg
<- u[1];
S <- u[2];
E # the number of compartments to model the duration of infectiousness
<- p[1]
nstg for (i in 1:nstg) {
eval(str2lang(paste0("I",i," <- ", "u[2+",i,"]")));
} <- u[2 + nstg + 1];
R <- u[2 + nstg + 2];
C
# population size
<- paste(sapply(1:nstg, function(x) paste0("I",x)), collapse="+")
istr_sum eval(str2lang(paste0("pop <- S+E+R+", istr_sum)))
<- p[2] # 1/latent period
epsilon <- p[3] # 1/duration of infectiousness
gamma <- p[4] # transmission rate
beta <- p[5] # 1/omega = duration of natural immunity
omega # force of infection
eval(str2lang(paste0("foi <- beta*(", istr_sum, ")/pop")))
<- epsilon
muEI <- gamma
muIR <- omega
muRS
# differential equations
<- - foi*S + muRS*R
dS <- foi*S - muEI*E
dE <- muEI*E - nstg*muIR*I1
dI1
#dI2, ...
if (nstg >= 2) {
for (i in 2:nstg) {
eval(str2lang(paste0("dI",i," <- ",
"*muIR*(I", i-1, "-I", i, ")")))
nstg,
}
}# dR
eval(str2lang(paste0("dR <- muIR*I", nstg, "-muRS*R")))
<- muEI*E
dC
<- paste(sapply(1:nstg, function(x) paste0("dI", x)), collapse=",")
distr return(eval(str2lang(paste0("c(dS,dE,", distr, ",dR,dC)"))))
}
Simulation of the variable-compartment model
<- function(nstg) {
run_seir_nstg <- 2 # basic reproduction number
R0 <- 1/4 # 1/epsilon = incubation period
epsilon <- 1/7 # 1/gamma = duration of infectiousness
gamma <- R0*gamma # instantaneous transmission rate
beta <- 1/(4*365) # natural immunity waning rate
omega # parameters
<- c(nstg=nstg, epsilon=epsilon, gamma=gamma, beta=beta, omega=omega)
params # initial distribution of the population across the states
<- paste(sapply(1:nstg, function(x) paste0("I", x,"=0")), collapse=",")
I0s eval(str2lang(paste0("u0 <- c(S=0,E=0,", I0s,",R=0,C=0)")))
c("S","E","I1","R")] <-
u0[c(0.99, 0, 0.01, 0) # all I in the first compartment of the nstg I compartments
<- 100 #
tend <- c(0.0, tend)
tspan
<- de$ODEProblem(seir_nstg, u0, tspan, params)
prob <- de$solve(prob, de$Tsit5(), saveat=1)
sol
<- sapply(sol$u, identity)
mat <- as.data.frame(t(mat))
udf <- cbind(data.frame(t=sol$t), udf)
out <- paste(sapply(1:nstg, function(x) paste0('"I', x, '"')), collapse=",")
In names(out) <- eval(str2lang(paste0('c("t","S","E",', In, ',"R","C")')))
return(out)
}
<- 1
nstg <- paste(sapply(1:nstg, function(x) paste0('"I', x, '"')), collapse=",")
In <- run_seir_nstg(nstg=nstg)
out1 <- out1[,"I1"]
It1
<- 2
nstg <- paste(sapply(1:nstg, function(x) paste0('"I', x, '"')), collapse=",")
In <- run_seir_nstg(nstg=nstg)
out2 eval(str2lang(paste0('It2 <- rowSums(out2[,c(', In, ')])')))
<- 5
nstg <- paste(sapply(1:nstg, function(x) paste0('"I', x, '"')), collapse=",")
In <- run_seir_nstg(nstg=nstg)
out5 eval(str2lang(paste0('It5 <- rowSums(out5[,c(', In, ')])')))
Plot the simulation results.
# png(filename="seir_multi_comp.png")
plot(1:nrow(out), out$I, type="l", ylim=c(0,0.2),
ylab="fraction infectious",
xlab="day", col="black")
lines(1:nrow(out), It1, col="steelblue")
lines(1:nrow(out), It2, col="firebrick")
lines(1:nrow(out), It5, col="magenta")
legend("topright",
legend=c("SEIR","1 compartment","2 compartments", "5 compartments"),
col=c("black","steelblue", "firebrick","magenta"),
lty= 1,
bty = "n",
cex = 1.0,
text.col = "black",
horiz = F ,
inset = c(0.02,0.02))
# dev.off()