Repository navigation
Writing and Debugging EpiModel Code
Who this is for: anyone changing model code in EpiModelHIV-p (or EpiModelCOVID). Before you start: a working project from Getting Started with EpiModelHIV.
This page describes how we write and debug EpiModel module code interactively. The examples use EpiModelHIV in a project built from the EpiModelHIV-Template, but the same tools work for EpiModelCOVID and for any model built with EpiModel. If your model runs without errors but produces strange results, continue with Diagnostic of an EpiModel Module after this page.
For general background on debugging in R, see the debugging chapter of Advanced R and the RStudio debugging guide.
Every applied project pairs a project repo (scripts, inputs, and outputs) with a software repo (your branch of EpiModelHIV-p, which holds the modules). Keep both open in separate RStudio windows, each opened from its .Rproj file. You edit modules in the software repo and run them from scripts in the project repo, so after every edit the project's R session has to load the new code. There are two ways to do that.
pkgload::load_all() loads a package directly from its source folder without installing it. The Template wraps it in a helper, load_local_EpiModelHIV(), which reads the location of your EpiModelHIV-p clone from the EPIMODELHIV_DIR variable in the project's .Renviron file. To set it up once, run usethis::edit_r_environ("project"), add a line such as EPIMODELHIV_DIR="~/git/EpiModelHIV-p", save the file, and restart R.
The development loop, as in the Template's R/B-model_dev/2-debug_modules.R, is:
- Edit a module in your EpiModelHIV-p clone and save the file.
- In the project repo, reload the package with
load_local_EpiModelHIV(). - Re-create the control settings with
control_msm(). - Run
netsim().
load_local_EpiModelHIV()
source("R/shared_variables.R", local = TRUE)
source("R/B-model_dev/z-context.R", local = TRUE)
source("R/netsim_settings.R", local = TRUE) # creates param and init
est <- readRDS(path_to_est)
control <- control_msm(nsteps = 52, ncores = 1)
sim <- netsim(est, param, init, control)Step 3 matters. control_msm() stores the module functions themselves (for example control$hivtest.FUN), not their names. A control object created before the reload keeps running the old version of every module, and debugonce() on the reloaded function will not trigger. You do not need to re-create param, init, or the network estimates.
Two things to know about load_all():
- Always use
ncores = 1. Withncores > 1,netsim()runs simulations in separate R workers, and those workers load the installed package, not your development code. - It lasts only for the current R session and only on your computer. To run the new code on the HPC, or to share it, install it into the project library as described next.
- Commit the change in the software repo and push it to your branch on GitHub.
- In the project repo, restart R (Session > Restart R, or Cmd+Shift+0 on a Mac, Ctrl+Shift+F10 on Windows).
- Install your branch into the project's renv library with
renv::install("EpiModel/EpiModelHIV-p@<your-branch>"). The GitHub repository is calledEpiModelHIV-p, but the R package it contains isEpiModelHIV. The repository is private, so this needs a GitHub token; see Using renv for Reproducible R Environments. - Restart R and run your scripts from the top, this time with
library(EpiModelHIV)instead ofload_local_EpiModelHIV(). - If everything works, record the new version with
renv::snapshot(), then commit and pushrenv.lock.
The HPC runs whatever version is installed in your local project library: the workflow scripts build renv.lock.hpc from it (see Running Project Workflows on the HPC). Code that you have only loaded with load_all() never reaches the cluster.
When you change the engine, also bump the Version field in EpiModelHIV-p's DESCRIPTION (for example from 3.3.2 to 3.3.2.9000), so that installed builds can be told apart with packageVersion("EpiModelHIV").
load_local_EpiModelHIV()
control <- control_msm(nsteps = 52, ncores = 1)
debugonce(hivtest_msm)
sim <- netsim(est, param, init, control)When netsim() reaches the HIV testing module, R pauses on the first line of hivtest_msm(), opens the function in the editor, and changes the console prompt to Browse[1]>. A toolbar appears above the console with Next (n), Step into function (s), Finish function or loop (f), Continue (c), and Stop (Q). You can click the buttons or type the letters at the prompt.
While paused, you can:
- run the module one line at a time with Next;
- print any object by typing its name at the prompt, for example
table(race)orsummary(hiv.test.last); - see the module's local variables in the Environment pane;
- try new code by typing it at the prompt, or by selecting lines in the editor and running them with Cmd+Enter (Ctrl+Enter on Windows).
Editing the source file while paused does not change the code that is running. When you have a fix, save the file, reload with load_local_EpiModelHIV(), re-create control, and run netsim() again.
debugonce() pauses only the first time the function is called. debug(hivtest_msm) pauses every time it is called, which for a module means every time step, until you run undebug(hivtest_msm).
To start in the middle of a module, or only when some condition holds, add a call to browser() in the module source. For example, in hivtest_msm(), after test_pos_ids is created:
if (at == 25 && length(test_pos_ids) > 0) browser()Save the file, reload, re-create control, and run netsim(). R pauses at that line at time step 25 (at is the current time step), and only if someone tested positive. Any condition works. Remove the browser() line before you commit.
Our models are stochastic, so an error may appear at step 10 in one run and at step 57 in the next. Instead of stepping through every time step, tell R to pause wherever the error happens:
options(error = recover)
sim <- netsim(est, param, init, control)When an error occurs, netsim() reports the module and time step, and R lists the calls that were running at that moment. For example, after adding if (at == 4) get_param(dat, "new.param") to the top of hivtest_msm():
A ERROR occured in module 'hivtest.FUN' at step 4
Error in get_param(dat, "new.param") :
There is no parameter called `new.param` in the parameter list of the main list object (dat)
Enter a frame number, or 0 to exit
1: netsim(est, param, init, control)
2: Map(netsim_run, dat = dat_list, s = seq_along(dat_list))
3: mapply(FUN = f, ..., SIMPLIFY = FALSE)
4: (function (dat, s = 1)
{
while (get_current_timestep(dat) < get_control(
5: netsim_run_nsteps(dat, steps_to_run, s)
6: netsim_run_modules(dat, s)
7: withCallingHandlers(expr = {
current_mod <- "epimodel.internal"
dat <
8: modules[[i]](dat, at)
9: mod.hivtest.R#18: get_param(dat, "new.param")
Selection:
Frames 1 to 7 are EpiModel's simulation loop. The frame that calls the module (here, frame 8, which shows (dat, at) as its arguments) is the module that failed: enter its number to browse inside hivtest_msm() as it was at the moment of the error, with dat, at, and all of the module's local variables available. The last frame is the call that raised the error; when the package is loaded with load_all(), it is labeled with the file and line number (mod.hivtest.R#18). If the error happened inside a helper function that your module calls, the helper gets its own frame after the module's. Enter 0 to leave the menu. When you are done, switch this behavior off with options(error = NULL).
recover needs an interactive session. For runs on the HPC, or with ncores > 1, add two control settings so that a failing simulation leaves evidence behind:
control <- control_msm(
nsteps = 52 * 10,
.traceback.on.error = TRUE,
.dump.frame.on.error = TRUE
).traceback.on.error prints the full call stack to the console or job log. .dump.frame.on.error saves the state of every frame of the failing simulation to a file named like dump_20261012_100411_1.rda in the working directory. Copy that file to your computer and explore it with:
library(EpiModelHIV)
load("dump_20261012_100411_1.rda")
debugger(last.dump)debugger() shows the same frame menu as recover. Dump files contain the whole dat object, so they can be large; delete them when you are done.
EpiModel Research Lab wiki. Something out of date or unclear? Post in #coding-help on the lab Slack, or fix it yourself: see Contributing to This Wiki.
0. Start here
1. Your computer
2. Learn EpiModel
3. First project
4. Model code
5. HPC
- Access and login
- Environment setup
- Rules, storage, and help
- Files
- Git
- R via Spack
- srun
- sbatch
- Monitoring jobs
- Project workflows
- Checkpoints
6. Calibrate and analyze
7. Publish
8. Admin