Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

https://github.com/lshtm-tbmg/tbmod-pub-aTBvaccines
23 February 2026, 09:39:33 UTC
  • Code
  • Branches (1)
  • Releases (0)
  • Visits
    • Branches
    • Releases
    • HEAD
    • refs/heads/main
    No releases to show
  • 1a586a8
  • /
  • 0_AutoEmulate.R
Raw File Download Save again
Take a new snapshot of a software origin

If the archived software origin currently browsed is not synchronized with its upstream version (for instance when new commits have been issued), you can explicitly request Software Heritage to take a new snapshot of it.

Use the form below to proceed. Once a request has been submitted and accepted, it will be processed as soon as possible. You can then check its processing state by visiting this dedicated page.
swh spinner

Processing "take a new snapshot" request ...

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
  • revision
  • snapshot
origin badgecontent badge
swh:1:cnt:99125bed9138a6e8f1414c367f5abee8ec5b4a59
origin badgedirectory badge
swh:1:dir:1a586a871098f12f81209577f48b3ee4b816619d
origin badgerevision badge
swh:1:rev:053ca109710a4d2792027a0e95a660f68c81e7a5
origin badgesnapshot badge
swh:1:snp:8ccd21bde57b8a0593af606bf10b8b38a4db291f

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
  • revision
  • snapshot
(requires biblatex-software package)
Generating citation ...
(requires biblatex-software package)
Generating citation ...
(requires biblatex-software package)
Generating citation ...
(requires biblatex-software package)
Generating citation ...
Tip revision: 053ca109710a4d2792027a0e95a660f68c81e7a5 authored by Rebecca Clark on 15 December 2025, 16:57:33 UTC
updates: files to run aTB and vaccines model
Tip revision: 053ca10
0_AutoEmulate.R
### AutoEmulate (Redo)

### Sample usage: Rscript AutoEmulateRerun.R -c AFG -p input.csv -t target_em.csv -x XMLinput.xml -n 10 -s 0 -m 1000 -f 0.4
### Details of flags:
## -c ISO Country code (Required)
## -p Parameter file including .csv extension (Default input.csv)
## -t Target file including .csv extentson (Default target.csv)
## -x XML file including .xml extension (Default XMLinput_target.xml)
## -n Maximum number of waves (Default 15)
##
## -m Minimum desired number of full fits (Default 1000)
## -f Proportion of full fits at a wave to trigger full-fit mining (Default 0.4)
## -s Proportion of runs at mining stage that target good runs (Default 0, EXPERIMENTAL)

rm(list = ls())

fileversion = "5.5.3 (Deja Vu)"

library(here)
library(lhs)
library(hmer)
library(log4r)
library(getopt)
library(tbmod)
packageVersion("tbmod")

#print("renv details:")
#print(renv::paths$root())
#print(renv::paths$library())
#print(renv::paths$cache())

### HELPER FUNCTIONS ###
# Determines how many of the outputs for a point are within bounds 
in.bounds <- function(point, targets, out_stat = "sum") {
  fits <- purrr::map_lgl(names(targets), function(i) {
    as.numeric(point[[i]]) <= targets[[i]][2] && as.numeric(point[[i]]) >= targets[[i]][1]
  })
  eval_func <- get(out_stat)
  return(eval_func(fits))
}

# Wrapper for the read.csv options, for brevity
r.csv <- function(filename) {
  df <- read.csv(filename, stringsAsFactors = FALSE, fileEncoding = "UTF-8-BOM", header = TRUE)
  return(df)
}

# Generates a csv containing the varying and non-varying parameters, as well as the outputs
gen.csv <- function(results, input) {
  all.params <- r.csv(here("countries", opts$country, "parameters", input))
  param.names <- all.params$unique.name
  c.params <- setNames(all.params[!all.params$choose, "mean"], all.params[!all.params$choose, "unique.name"])
  for (i in 1:length(c.params)) {
    results[[names(c.params)[i]]] <- c.params[[i]]
  }
  output.names <- names(results)[!names(results) %in% param.names]
  results <- results[,c(param.names, output.names)]
  results$uid <- apply(results, 1, function(x) digest::digest(paste0(x, collapse = ""), 'md5', serialize = FALSE))
  return(results)
}

## Need to check this for compatibility with new model version
# A wrapper for the running of the model; includes functionality to remove 'bad' points
run.model <- function(points, wave, res) {
  all_outputs <- apply(points, 1, function(x) run(paths, x, FALSE)$hits)
  check_null <- purrr::map_lgl(all_outputs, ~!is.null(.))
  all_outputs <- all_outputs[check_null]
  points <- points[check_null,]
  which.include <- lapply(all_outputs, nrow) == length(target.bounds)
  all_outputs <- all_outputs[which.include]
  output_values <- do.call('rbind', purrr::map(all_outputs, ~.$model))
  output_hits <- do.call('rbind', purrr::map(all_outputs, ~.$fit))
  nhits <- apply(output_hits, 1, sum)
  res[[wave]] <- cbind(points[which.include,], setNames(data.frame(output_values), names(target.bounds)))
  df_to_write <- cbind(res[[wave]], setNames(data.frame(output_hits), paste0(names(target.bounds), "hit")))
  df_to_write$nhits <- nhits
  is.valid <- apply(output_values, 1, function(x) all(x >= 0))
  res[[wave]] <- res[[wave]][is.valid,]
  df_to_write <- df_to_write[is.valid,]
  write.csv(gen.csv(df_to_write, opts$parameters), file = here("countries", opts$country, "output", paste0(RUNID_TS, "wave", wave-1, ".csv")), row.names = FALSE)
  return(res)
}

# Exchanges RData files
swap.files <- function(which.in, which.out, msg = NULL) {
  if (file.exists(which.out)) file.remove(which.out)
  save.image(file = which.in)
  if (!is.null(msg)) emulator.log(msg = msg)
}

# Initialise the model
#model = new.env()
#source(here("R", "include-v11.R"), model)
#source(here("R", "TBVx-run-v1.R"), model)

#Get options from command line (see start for option specifics)
# opts <- getopt(
#   matrix(
#     c('country', 'c', 1, 'character',
#       'parameters', 'p', 2, 'character',
#       'targets', 't', 2, 'character',
#       'xml', 'x', 2, 'character',
#       'n_waves', 'n', 2, 'integer',
#       'seek_percent', 's', 2, 'double',
#       'min_fits', 'm', 2, 'integer',
#       'mine_trigger', 'f', 2, 'double'
#     ),
#     byrow = TRUE,
#     ncol = 4
#   )
# )

opts <- list()
opts$country <- "IND"
opts$parameters <- "input_baseline.csv"
opts$targets <- "target_aTB.csv"
opts$xml <- "XMLinput_Calibration.xml"



# For cluster work: finds any backup files to load from
job.ident <- sub("^[1234567890-]*_(.*)", "\\1", Sys.getenv("RUNID_TS"))
backup.list <- grepl(paste0(".*", job.ident, "_backup.RData"), dir(here("countries", opts$country, "output")))
is.backed <- any(backup.list)
if (is.backed) {
  backup.file <- dir(here("countries", opts$country, "output"), full.names = TRUE)[backup.list]
  if (sum(backup.list) > 1)
    backup.file <- backup.file[order(file.info(backup.file)$ctime, decreasing = TRUE)][1]
  else backup.file <- backup.file[1]
  load(backup.file)
  OLD_RUNID_TS <- RUNID_TS
  is.backed <- TRUE
}

RUNID_TS <- Sys.getenv("RUNID_TS")
if (RUNID_TS == "")
  RUNID_TS <- sprintf("%s_%s_%s", format.Date(Sys.time(), format = "%Y-%m-%d-%H%M", tz = "UTC"), "LOCAL", opts$country)

if (is.backed) file.remove(backup.file.name)
backup.file.name <- here("countries", opts$country, "output", paste0(RUNID_TS, "_backup.RData"))
failure.file.name <- here("countries", opts$country, "output", paste0(RUNID_TS, "_failed.RData"))
complete.file.name <- here("countries", opts$country, "output", paste0(RUNID_TS, "_complete.RData"))

if (is.backed) save.image(file = backup.file.name)

dir.create(here("countries", opts$country, "logs"), showWarnings = FALSE)
dir.create(here("countries", opts$country, "output"), showWarnings = FALSE)

logr.emulator <- create.logger(logfile = here("countries", opts$country, "logs", paste0(RUNID_TS, "_emulator.log")), level = "INFO")
emulator.log <- function(level = "INFO", msg = NULL) levellog(logr.emulator, level = level, message = msg)
emulator.log(msg = paste("This is AutoEmulate version", fileversion))

tfile <- if(is.null(opts$targets)) "target.csv" else opts$targets
pfile <- if(is.null(opts$parameters)) "input.csv" else opts$parameters
xfile <- if(is.null(opts$xml)) "XMLinput_target.xml" else opts$xml

paths <- set.paths(countrycode = opts$country, xml = xfile, parameters = pfile, targets = tfile)

# Gets the data from the input.csv and target.csv files for use in emulation
tryCatch(
  {
    param.data <- subset(r.csv(here("countries", opts$country, "parameters", pfile)), choose == TRUE)
    ranges <- setNames(purrr::map(1:nrow(param.data), ~c(param.data[.,'min'], param.data[.,'max'])), param.data$unique.name)
    target.data <- subset(r.csv(here("countries", opts$country, "parameters", tfile)), err != -1)
    target.bounds <- setNames(purrr::map(1:nrow(target.data), ~c(target.data[.,'lo'], target.data[.,'hi'])), paste0(target.data[,'name'], target.data[,'year']))
  },
  error = function(e) {
    emulator.log("FATAL", "Could not read parameter and/or target file - missing or misspecified.")
    stop("Error reading parameter and/or target csvs.")
  }
)

n_points <- 20*length(ranges)
#n_points <- 5
em.ranges <- ranges
em.targets <- target.bounds

# First runs: generate an LHS and run the model for the first time.
if (!is.backed) {
  emulator.log(msg = "Finished preliminary setup. Beginning first model runs.")
  results.list <- list()
  emulators.list <- c()
  targets.list <- c()
  initial.points <- setNames(data.frame(t(apply(lhs::maximinLHS(5*n_points/4, length(ranges)), 1, function(x) {
    purrr::map_dbl(em.ranges, ~.[1]) + x * purrr::map_dbl(em.ranges, diff)
  }))), names(ranges))
  
  # Test run:
  #test_run <- run(paths = paths, new.parameter.values = initial.points[1,])
  
  tryCatch(
    results.list <- run.model(initial.points, 1, results.list),
    error = function(e) {
      emulator.log(level = "FATAL", msg = paste("Model runs failed:", e))
      stop("Model runs failed. See emulator log for failure details.")
    }
  )
} else {
  emulator.log(msg = paste("Restarted from save state", OLD_RUNID_TS, "- see corresponding logs."))
  emulator.log(msg = paste("Backup file", backup.file, "deleted and replaced with", backup.file.name))
}

if (nrow(results.list[[1]]) > n_points) results.list[[1]] <- results.list[[1]][sample(1:nrow(results.list[[1]]), n_points),]

# Initialization of options for emulation
kicked_out <- FALSE
n_waves <- if(is.null(opts$n_waves)) 15 else opts$n_waves
minimum_points <- if (is.null(opts$min_fits)) 1000 else opts$min_fits
seek_percent <- if (is.null(opts$seek_percent)) 0 else opts$seek_percent
mine_trigger <- if (is.null(opts$mine_trigger)) 0.4 else opts$mine_trigger

if (n_waves >= length(results.list)) {
  i <- length(results.list)
  while (i <= n_waves) {
    emulator.log(msg = paste("Wave", i, "starting. Training emulators."))
    # Sets up the ranges: if first wave, then use the ranges. If not, then find the minimum enclosing hyperrectangle of the current runs (plus/minus 5%)
    if (i != 1) {
      e.ranges <- setNames(purrr::map(names(ranges), ~c(max(ranges[[.]][1], min(results.list[[i]][,.]) - 0.05 * diff(range(results.list[[i]][,.]))), min(ranges[[.]][2], max(results.list[[i]][,.]) + 0.05 * diff(range(results.list[[i]][,.]))))), names(ranges))
    } else {
      e.ranges <- ranges
    }
    # Sample points and train the emulators
    tryCatch(
      {
        ## This tries to include some full fit points, if they exist, in the training set (but not too many!)
        all_hits <- c(which(apply(results.list[[i]], 1, in.bounds, target.bounds) == length(target.bounds)), use.names = FALSE)
        if (length(all_hits) > 0) all_hits <- sample(all_hits, ceiling(length(all_hits)/2))
        if (length(all_hits) > max(length(ranges)*5, ceiling(n_points/4))) all_hits <- all_hits[sample(length(all_hits), max(length(ranges)*5, ceiling(n_points/4)))]
        if (length(all_hits) > 0) samp <- sample((1:nrow(results.list[[i]]))[-all_hits], max(length(ranges)*10, ceiling(n_points/2))-length(all_hits))
        else samp <- sample(1:nrow(results.list[[i]]), max(length(ranges)*10, ceiling(n_points/2)))
        train <- results.list[[i]][c(all_hits, samp),]
        valid <- results.list[[i]][-c(all_hits, samp),]
        # Train the emulators using train as the dataset
        ems <- emulator_from_data(train, names(em.targets), e.ranges)
      },
      error = function(e) {
        swap.files(failure.file.name, backup.file.name, paste("Emulator training failed. Check by hand.", e))
        kicked_out <<- TRUE
        stop("Emulator training failed. Error details are in emulator log.")
      }
    )
    if (kicked_out) break
    # This just ensures that the emulator variance hasn't fallen off a cliff (simple regularisation)
    for (j in 1:length(ems)) {
      if (ems[[j]]$u_sigma < 1e-8) ems[[j]] <- ems[[j]]$set_sigma(sqrt(1e-7))
    }
    # Debugging flag: if there's an emulator with no dependence on parameters, then something has likely gone weird (but might not have)
    if (any(purrr::map_dbl(ems, ~length(.$basis_f)) == 1))
      emulator.log("WARN", "One or more trained emulators have no dependence on parameters. This could be due to anomalous model points.")
    # Diagnostics
    emulator.log(msg = paste("Wave", i, "emulators trained. Performing diagnostics."))
    # Check misclassifications, and inflate variance until no such misclassifications exist
    for (j in 1:length(ems)) {
      misclass <- nrow(classification_diag(ems[[j]], em.targets, valid, plt = FALSE))
      while(misclass > 0) {
        ems[[j]] <- ems[[j]]$mult_sigma(1.1)
        misclass <- nrow(classification_diag(ems[[j]], em.targets, valid, plt = FALSE))
      }
    }
    bad.ems <- c()
    # Compare emulated to simulated output: emulators are dropped from a wave if they do not agree on at least 75% of the validation points
    for (j in 1:length(ems)) {
      tryCatch(
        {
          bad.model <- nrow(comparison_diag(ems[[j]], em.targets, valid, plt = FALSE))
          if (bad.model > floor(nrow(valid)/4)) {
            bad.ems <- c(bad.ems, j)
            emulator.log("WARN", paste("Emulator for output", ems[[j]]$output_name, "does not pass comparison diagnostics and will not be used at this wave."))
          }
        },
        error = function(e) {
          bad.ems <- c(bad.ems, j)
          emulator.log("WARN", paste("Emulator for output", ems[[j]]$output_name, "does not pass comparison diagnostics and will not be used at this wave."))
        }
      )
    }
    ems <- ems[!seq_along(ems) %in% bad.ems]
    if (length(ems) == 0) {
      swap.files(failure.file.name, backup.file.name, paste("All emulators failed diagnostics at wave", i))
      stop("All emulators failed diagnostics.")
    }
    # This comes into play later, where we terminate early if the emulators are as certain as they're going to get
    uncertainty.products <- purrr::map_dbl(ems, ~6*sqrt(.$u_sigma^2 + diff(em.targets[[.$output_name]])^2/36)/diff(em.targets[[.$output_name]]))
    emulator.log(msg = paste("Emulator diagnostics complete.", length(ems), "targets will be matched to at this wave."))
    # Helpful to look at what proportion of previous points are viewed as acceptable at this wave
    accept.rate <- sum(nth_implausible(ems, results.list[[i]], em.targets, cutoff = 3))/nrow(results.list[[i]])
    emulator.log(msg = paste("Estimated acceptance rate for wave", i, "emulators considering wave", i-1, "points is", signif(accept.rate, 5)))
    emulator.log(msg = paste("Proposing new points from emulators at wave", i))
    # Point generation: most of the guts of this live in the generate_new_runs function now.
    tryCatch(
      new.points <- generate_new_runs(c(ems, emulators.list), n_points, em.targets),
      error = function(e) {
        swap.files(failure.file.name, backup.file.name, paste("Point generation failed", e))
        kicked_out <<- TRUE
      }
    )
    if (kicked_out) break
    # Diagnostic to log if the emulators had to propose at implausibility higher than 3
    max_imp <- signif(max(nth_implausible(c(ems, emulators.list), new.points, em.targets)), 4)
    emulator.log(msg = paste("Proposed points have implausibility no greater than", max_imp))
    emulator.log(msg = "Running the model for these new points.")
    # Run the new points!
    tryCatch(
      results.list <- run.model(new.points, i+1, results.list),
      error = function(e) {
        emulator.log("FATAL", paste("Model runs at wave", i, "failed:", e))
        kicked_out <<- TRUE
        stop(paste("Model runs failed at wave", i))
      }
    )
    if (kicked_out) break
    emulators.list <- c(ems, emulators.list)
    swap.files(backup.file.name, backup.file.name)
    # Full fit mining
    # If the emulators are super-certain about their outputs and if the last wave did emulate every output,
    # OR if these emulators proposed a large number of full fit points, then we stop training emulators and
    # simply propose from this wave.
    # This behaviour is superceded by the maximum number of waves (or else you can end up hunting in vain for
    # full fits where none exist; eg GUY)
    if ((all(uncertainty.products < 1.1) && length(ems) == length(target.bounds) && max_imp <= 3) || sum(apply(results.list[[length(results.list)]], 1, in.bounds, target.bounds) == length(target.bounds))/nrow(results.list[[length(results.list)]]) > mine_trigger) {
      emulator.log(msg = "Final wave (emulators have minimal uncertainty or producing many full fits) - looking for full fit points.")
      total.runs <- do.call('rbind', results.list)
      good.runs <- total.runs[which(apply(total.runs, 1, in.bounds, target.bounds) == length(target.bounds)),]
      index <- 1
      # It will continue to mine until it has the desired number of points
      while (nrow(good.runs) < minimum_points) {
        emulator.log(msg = paste("Currently", nrow(good.runs), "full fits have been found."))
        if (nrow(good.runs) != 0) {
          which.hits <- setNames(data.frame(t(apply(good.runs, 1, in.bounds, target.bounds, 'c'))), paste0(names(em.targets), "hit"))
          good.points <- cbind(good.runs, which.hits)
          good.points$nhits <- length(target.bounds)
          out.csv <- gen.csv(good.points, pfile)
          write.csv(out.csv, file = here("countries", opts$country, "output", paste0(RUNID_TS, "_inputPoints.csv")), row.names = FALSE)
        }
        how_many_extra <- 250
        more.points <- generate_new_runs(emulators.list, how_many_extra, em.targets, seek = floor(seek_percent * how_many_extra))
        results.list <- run.model(more.points, i+1+index, results.list)
        index <- index + 1
        total.runs <- do.call('rbind', results.list)
        good.runs <- total.runs[which(apply(total.runs, 1, in.bounds, target.bounds) == length(target.bounds)),]
      }
      which.hits <- setNames(data.frame(t(apply(good.runs, 1, in.bounds, target.bounds, 'c'))), paste0(names(em.targets), "hit"))
      good.points <- cbind(good.runs, which.hits)
      good.points$nhits <- length(target.bounds)
      out.csv <- gen.csv(good.points, pfile)
      write.csv(out.csv, file = here("countries", opts$country, "output", paste0(RUNID_TS, "_inputPoints.csv")), row.names = FALSE)
      break
    }
    i <- i + 1
  }
}

# Ending: if we didn't terminate early, then we write the results to file. If we did kick out early, then
# a failure RData is produced.
if (!kicked_out) {
  swap.files(complete.file.name, backup.file.name)
  emulator.log(msg = paste(i, "waves completed. Check inputPoints.csv for points."))
  if (!file.exists(here("countries", opts$country, "output", paste0(RUNID_TS, "_inputPoints.csv")))) {
    total.runs <- do.call('rbind', results.list)
    good.runs <- total.runs[which(apply(total.runs, 1, in.bounds, target.bounds) == length(target.bounds)),]
    if (nrow(good.runs) == 0) good.runs <- results.list[[length(results.list)]]
    which.hits <- setNames(data.frame(t(apply(good.runs, 1, in.bounds, target.bounds, 'c'))), paste0(names(target.bounds), "hit"))
    good.points <- cbind(good.runs, which.hits)
    good.points$nhits <- apply(good.runs, 1, in.bounds, target.bounds)
    out.csv <- gen.csv(good.points, pfile)
    write.csv(out.csv, file = here("countries", opts$country, "output", paste0(RUNID_TS, "_inputPoints.csv")), row.names = FALSE)
  }
} else {
  swap.files(failure.file.name, backup.file.name)
}

back to top

Software Heritage — Copyright (C) 2015–2026, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API