deSolve to denim

library(denim)
library(deSolve)
## 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) ), {
      gamma_rate = 1/scale
      
      dS = -beta*S*I/N
      dI1 = beta*S*I/N - gamma_rate*I1
      dI2 = gamma_rate*I1 - gamma_rate*I2
      dI =  dI1 + dI2
      dR = gamma_rate*I2
      list(c(dS, dI, dI1, dI2, dR))
  })
}

# ---- Model configuration 
parameters <- c(beta = 0.3, scale = 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

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, (ii) a mathematical expression, (iii) a fixed number or proportion.

User must first identify the transitions that 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) ), {
      gamma_rate = 1/scale
      
      # 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 - gamma_rate*I1
      dI2 = gamma_rate*I1 - gamma_rate*I2
      dI =  dI1 + dI2
      dR = gamma_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

# --- Transition def for denim
transitions <- list(
  "S -> I" = "beta * S * I/N * timestepDur",
  "I -> R" = d_gamma(3, 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 timestepDur in the model definition code). This is because we are essentially using Euler’s method to estimate the solution for the ODE.

Model configurations

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 and scale are already defined in the distribution functions, users only need to keep beta and N from the initial parameters vector. We also need to specify the value for timestepDur here.

# remove I1, I2 compartments
denim_initialValues <- c(S = 999, I = 1, R=0)
denim_parameters <- c(beta = 0.3, N = 1000, timestepDur=0.01) 

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.

Simulation

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).

mod <- sim(transitions = transitions,
             initialValues = denim_initialValues, 
             parameters = denim_parameters,
             simulationDuration = 100,
             timeStep = 0.01)

Compare output

The following plots show output from denim and deSolve