-
Notifications
You must be signed in to change notification settings - Fork 2
Diagnostic of an EpiModel Module
In this tutorial we explore an easy way to diagnose an EpiModel module. We assume that the module does not crash but produces weird results that we want to inspect directly. If the module is crashing the model, use options(error = recover)
to enter debug mode when the crash happens.
For this tutorial, we will use an EpiModelHIV
model and we will debug the position
module.
First we load the libraries as usual and define the param
and init
object.
library("EpiModelHIV")
# pkgload::load_all("../EpiModelHIV-p.git/DoxyPEP")
epistats <- readRDS("data/intermediate/estimates/epistats-local.rds")
netstats <- readRDS("data/intermediate/estimates/netstats-local.rds")
est <- readRDS("data/intermediate/estimates/netest-local.rds")
param <- param.net(
data.frame.params = read.csv("data/input/params.csv"),
netstats = netstats,
epistats = epistats
)
init <- init_msm(
prev.ugc = 0.1,
prev.rct = 0.1,
prev.rgc = 0.1,
prev.uct = 0.1
)
The main difference occurs in control
, where we set the argument raw.output
to TRUE
.
control <- control_msm(
nsteps = 100,
nsims = 1,
ncores = 1,
cumulative.edgelist = TRUE,
truncate.el.cuml = 0,
verbose = TRUE,
raw.output = TRUE ### NEW ###
)
This prevents netsim
from post processing the simulation at the end of the run. Leaving us with a list of raw dat
objects. These are the object used internally by the modules.
The simulation is ran as usual with a call to netsim
. However, here we save the result as dat_list
for clarity and create a dat
object by getting the first element of dat_list
.
dat_list <- netsim(est, param, init, control)
dat <- dat_list[[1]]
At this point, dat
is exactly the same thing as the dat
object on the last step of the netsim
simulation. We save it in order to bypass these steps next time.
saveRDS(dat, "dat.rds")
First, we load dat
from the saved file and allow the simulation to run for longer. To do so we add 100
to the dat$control$nsteps
.
dat <- readRDS("dat.rds")
dat$control$nsteps <- dat$control$nsteps + 100
Then we run a few more steps of simulation on dat
using the EpiModel
internal function netsim_run_nsteps
. Notice that we use :::
and not ::
here. This allow us to access unexported functions.
EpiModel:::netsim_run_nsteps(dat, 30, 1)
This stage is meant to generate a different state of dat
each time. Very useful when debugging a stochastic error.
Note: we cannot simply save the state of dat
with dat_sav <- dat
and then restore it with dat <- dat_sav
. That's because it contains C object that will be altered. Reloading the "dat.rds" file resets it correctly though.
Now we will want to put our dat
object exactly in the state it would be before entering the module we want to debug.
First, we set the at
time object to it's correct value.
at <- get_current_timestep(dat) + 1
Now we check how many modules needs to be run before we reach the module to debug. The _modules_list
elements to dat
contains a named list of modules.
names(dat[["_modules_list"]])
#>
#> [1] "aging.FUN" "departure.FUN" "arrival.FUN"
#> [4] "partident.FUN" "hivtest.FUN" "hivtx.FUN"
#> [7] "hivprogress.FUN" "hivvl.FUN" "resim_nets.FUN"
#> [10] "summary_nets.FUN" "acts.FUN" "condoms.FUN"
#> [13] "position.FUN" "prep.FUN" "hivtrans.FUN"
#> [15] "stitrans.FUN" "stirecov.FUN" "stiscreen.FUN"
#> [18] "stitx.FUN" "prev.FUN" "cleanup.FUN"
Here we see that position.FUN
, our module of interest is the 13th. Therefore, we need to run all modules from 1 to 12.
for (mod in dat[["_modules_list"]][1:12]) {
dat <- mod(dat, at)
}
Note: applying each module sequentially to dat
is actually what netsim
does at each timestep.
At this point, dat
is in the state it should before entering the 13th module (postion.FUN
).
Now we simply paste the module's code and run it interactively, inspecting each step as we need to find our error.
# position_msm <- function(dat, at) {
al <- dat[["temp"]][["al"]]
if (nrow(al) == 0) {
return(dat)
}
# Attributes
role.class <- get_attr(dat, "role.class")
ins.quot <- get_attr(dat, "ins.quot")
# Parameters
## Process
p1.role.class <- role.class[al[, "p1"]]
p2.role.class <- role.class[al[, "p2"]]
ins <- rep(NA, length(p1.role.class))
ins[which(p1.role.class == 0)] <- 1
ins[which(p1.role.class == 1)] <- 0
ins[which(p2.role.class == 0)] <- 0
ins[which(p2.role.class == 1)] <- 1
# Versatile MSM
vv <- which(p1.role.class == 2 & p2.role.class == 2)
p1.ins.prob <- ins.quot[al[, 1][vv]] /
(ins.quot[al[, 1][vv]] + ins.quot[al[, 2][vv]])
p1.ins <- runif(length(vv)) < p1.ins.prob
ins[vv[p1.ins]] <- 1
ins[vv[!p1.ins]] <- 0
## Output
dat[["temp"]][["al"]] <- cbind(al, ins)
return(dat)
# }