Skip to contents
## Warning: package 'deSolve' was built under R version 4.3.1

Migrate deSolve code to denim

Original code in deSolve

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

Model definition

Similar to deSolve, transitions between compartments in denim can be defined using ordinary differential equations (ODEs). However, denim extends it by providing the option to directly express the dwell time distribution.

To utilize this option from denim, the user must first identify which transitions can best describe the ones in their deSolve model.

Identify distribution
# --- 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, the user can then define the model in denim.

When using denim DSL, the model structure is given as a set of key-value pairs where

  • key shows the transition direction between compartments in the format of compartment -> out_compartment.

  • value is either a built-in distribution function that describe the transition or a mathematical expression.

# --- Model definition in denim
transitions <- denim_dsl({
  S -> I = beta * S * I/N
  # shape is 2 from number of I sub compartments
  I -> R = d_gamma(rate = 1/3, shape = 2) 
})

Model configurations

Similar to deSolve, denim also ask the users to provide the initial values and any additional parameters in the form of named vectors or named list.

For the example deSolve code, while the 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, the 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 <- denim_dsl({
  S -> I = beta * S * I/N 
  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.

Simulation

Lastly, the 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 uses a discrete time approach, time step must be set to a small value for the result to closely follow that of deSolve (in this example, 0.01).

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

Compare output

The following plots show output from denim and deSolve