## Warning: package 'deSolve' was built under R version 4.3.1
The model used for demonstrating the process of migrating code from
deSolve to denim is as followed
# --- Model definition in deSolve
transition_func <- function(t, state, param){
with(as.list( c(state, param) ), {
dS = -beta*S*I/N
dI1 = beta*S*I/N - rate*I1
dI2 = rate*I1 - rate*I2
dI = dI1 + dI2
dR = rate*I2
list(c(dS, dI, dI1, dI2, dR))
})
}
# ---- Model configuration
parameters <- c(beta = 0.3, rate = 1/3, N = 1000)
initialValues <- c(S = 999, I = 1, I1 = 1, I2=0, R=0)
# ---- Run simulation
times <- seq(0, 100) # simulation duration
ode_mod <- ode(y = initialValues, times = times, parms = parameters, func = transition_func)
# --- show output
ode_mod <- as.data.frame(ode_mod)
head(ode_mod[-1, c("time", "S", "I", "R")])## time S I R
## 2 1 998.6561 1.294182 0.04969647
## 3 2 998.2239 1.594547 0.18152864
## 4 3 997.6985 1.921416 0.38010527
## 5 4 997.0695 2.290735 0.63971843
## 6 5 996.3225 2.716551 0.96093694
## 7 6 995.4387 3.212577 1.34869352
Unlike deSolve where transitions between compartments
are defined by a system of ODEs, transitions in denim must
be defined by (i) a distribution of dwell-time (either parametric or
non-parametric) or (ii) a mathematical expression.
User must first identify the transitions that best describe the ones
in their deSolve model.
# --- Model definition in deSolve
transition_func <- function(t, state, param){
with(as.list( c(state, param) ), {
# For S -> I transition, since it involves parameters (beta, N),
# the best transition to describe this is using a mathematical formula
dS = -beta*S*I/N
# For I -> R transition, linear chain trick is applied --> implies Erlang distributed dwell time
# Hence, we can use d_gamma from denim
dI1 = beta*S*I/N - rate*I1
dI2 = rate*I1 - rate*I2
dI = dI1 + dI2
dR = rate*I2
list(c(dS, dI, dI1, dI2, dR))
})
}With the transitions identified, user can then define the model in
denim.
When using denim, the model structure is given as a
list of key-value pairs where
key is a string showing the transition direction between compartments
value is the built-in distribution function that describe the transition
# --- Transition def for denim
transitions <- list(
"S -> I" = "beta * S * I/N * timeStep",
"I -> R" = d_gamma(rate = 1/3, shape = 2) # shape is 2 from number of I sub compartments
)Note that when converting an ODE to a math expression, we must
multiply the original expression with time step duration (variable
timeStep in the model definition code). This is because we
are essentially using Euler’s method to estimate the solution for the
ODE.
Similar to deSolve, denim also ask users to
provide the initial values and any additional parameters
in the form of named vectors.
For the example deSolve code, while users can use the
initalValues from the deSolve code as is
(denim will ignore unused I1, I2
compartments as these sub-compartments will be automatically computed
internally), it is recommended to remove redundant compartments (in this
example, I1 and I2).
For parameters, since rate is already defined in the
distribution functions, users only need to keep beta and
N from the initial parameters vector. We do not need to
specify the value for timeStep variable as this is a
special variable in denim and will be defined later on.
# remove I1, I2 compartments
denim_initialValues <- c(S = 999, I = 1, R=0)
denim_parameters <- c(beta = 0.3, N = 1000) Initialization of sub-compartments: when
there are multiple sub-compartments (e.g., compartment I
consist of I1 and I2 sub-compartments), the
initial population is always assigned to the first sub-compartment. In
our example, since I = 1, denim will assign
I1 = 1 and I2 = 0.
There is also an option to distribute initial value across
sub-compartments based on the specified distribution. To do this, simply
set dist_init parameter of distribution function to
TRUE.
transitions <- list(
"S -> I" = "beta * S * I/N * timeStep",
"I -> R" = d_gamma(rate = 1/3, shape = 2, dist_init = TRUE)
)However, for comparison purposes, we will keep this option
FALSE for the remaining of this demonstration.
Lastly, users need to define the simulation duration and time step
for denim to run. Unlike deSolve which takes a
time sequence, denim only require the simulation
duration and time step.
Since denim is a discrete time model, time step must be
set to a small value for the result to closely follow that of
deSolve (in this example, 0.01).
Note that the value for parameter timeStep is also the
value for variable timeStep in the mathematical
equation.
mod <- sim(transitions = transitions,
initialValues = denim_initialValues,
parameters = denim_parameters,
simulationDuration = 100,
timeStep = 0.01)
head(mod[mod$Time %% 1 == 0, ])## Time S I R
## 1 0 999.0000 1.000000 0.00000000
## 101 1 998.6566 1.293759 0.04961535
## 201 2 998.2251 1.593725 0.18121212
## 301 3 997.7004 1.920189 0.37940195
## 401 4 997.0725 2.289061 0.63848007
## 501 5 996.3266 2.714365 0.95900520
The following plots show output from denim and
deSolve