tests/testthat/test-run_trials.R

test_that("single trial simulation works", {
  # Store seed - check that the entire process does not change it
  set.seed(12345)
  oldseed <- get(".Random.seed", envir = globalenv())

  setup <- read_testdata("binom__setup__3_arms__no_control__equivalence__softened")
  res <- read_testdata("binom__result__3_arms__no_control__equivalence__softened")
  expect_equal(run_trial(setup, seed = 12345, sparse = FALSE), res)

  setup <- read_testdata("binom__setup__3_arms__common_control__equivalence__futility__softened")
  res <- read_testdata("binom__result__3_arms__common_control__equivalence__futility__softened")
  expect_equal(run_trial(setup, seed = 12345, sparse = FALSE), res)

  setup_equi_futil_only_first <- setup_trial_binom(
    arms = c("A", "B", "C"),
    control = "B",
    control_prob_fixed = 1/3,
    fixed_probs = c(NA, 1/3, NA),
    true_ys = c(0.2, 0.21, 0.7),
    data_looks = seq(from = 500, to = 2000, by = 500),
    equivalence_prob = 0.9,
    equivalence_diff = 0.25,
    equivalence_only_first = TRUE,
    futility_prob = 0.95,
    futility_diff = 0.05,
    futility_only_first = TRUE
  )
  expect_snapshot(run_trial(setup_equi_futil_only_first, seed = 12345))

  setup_equi_futil_only_first2 <- setup_trial_binom(
    arms = c("A", "B", "C"),
    control = "B",
    control_prob_fixed = c(1/3, 1/2),
    fixed_probs = c(NA, 1/3, NA),
    true_ys = c(0.2, 0.21, 0.7),
    data_looks = seq(from = 500, to = 2000, by = 500),
    equivalence_prob = 0.9,
    equivalence_diff = 0.25,
    equivalence_only_first = TRUE,
    futility_prob = 0.95,
    futility_diff = 0.05,
    futility_only_first = TRUE
  )
  expect_snapshot(run_trial(setup_equi_futil_only_first, seed = 12345))

  # Check that trial with rescaled probabilities works
  setup_rescale_probs <- setup_trial_binom(
    arms = c("A", "B", "C"),
    control = "B",
    start_probs = c(0.4, 0.3, 0.3),
    fixed_probs = c(0.4, NA, NA),
    min_probs = c(NA, 0.15, 0.15),
    max_probs = c(NA, 0.85, 0.85),
    rescale_probs = "both",
    true_ys = c(0.2, 0.15, 0.3),
    data_looks = seq(from = 500, to = 2000, by = 500)
  )
  expect_snapshot(run_trial(setup_rescale_probs, seed = 12345))

  # Check that seed is unchanged
  expect_identical(oldseed, get(".Random.seed", envir = globalenv()))
})

test_that("Single trial simulation errors on invalid inputs", {
  setup <- read_testdata("binom__setup__3_arms__no_control__equivalence__softened")
  setup_wrong <- setup
  class(setup_wrong) <- "list"
  expect_error(run_trial(setup_wrong, seed = 4131))
  expect_error(run_trial(setup, seed = "invalid"))
  expect_error(run_trial(setup, sparse = NA))
})

test_that("dispatch_trial_runs works", {
     setup <- read_testdata("binom__setup__3_arms__common_control__equivalence__futility__softened")

     # Manage random seeds
     old_rngkind <- RNGkind("L'Ecuyer-CMRG", "default", "default")
     set.seed(12345)
     seeds <- list(get(".Random.seed", envir = globalenv()))
     for (i in 2:5) {
       seeds[[i]] <- nextRNGStream(seeds[[i - 1]])
     }

     # Serial run
     expect_snapshot(
       dispatch_trial_runs(1:5, setup, seeds = seeds, sparse = FALSE, cores = 1)
     )

     # Parallel run
     # Test only run conditionally, see check_cluster_version() function for
     # explanation.
     cl <- parallel::makeCluster(2)
     on.exit(parallel::stopCluster(cl))
     parallel::clusterEvalQ(cl, RNGkind("L'Ecuyer-CMRG", "default", "default"))
     if (check_cluster_version(cl)) {
       expect_snapshot(
         dispatch_trial_runs(1:5, setup, seeds = seeds, sparse = TRUE, cores = 2, cl = cl)
       )
     }

     RNGkind(kind = old_rngkind[1], normal.kind = old_rngkind[2], sample.kind = old_rngkind[3])
})

test_that("Multiple trials simulation works", {
  # Store seed - check that the entire process does not change it
  set.seed(12345)
  oldseed <- get(".Random.seed", envir = globalenv())

  setup <- read_testdata("binom__setup__3_arms__no_control__equivalence__softened")
  res <- run_trials(setup, n_rep = 20, base_seed = 12345, sparse = FALSE)

  sink_file <- tempfile() # diverts progress bar to not distort test output
  on.exit(try(file.remove(sink_file)), add = TRUE, after = FALSE)
  sink(sink_file)
  res_with_progress <- run_trials(setup, n_rep = 20, base_seed = 12345, sparse = FALSE, progress = 0.1)
  sink()

  loaded_res <- read_testdata("binom__results__3_arms__no_control__equivalence__softened")

  # Harmonise items known to be problematic (run-time and functions)
  for (x in c("res", "loaded_res", "res_with_progress")) {
    temp_x <- get(x)
    temp_x$elapsed_time <- as.difftime(0, units = "secs")
    for (f in c("fun_y_gen", "fun_draws", "fun_raw_est"))
      temp_x$trial_spec[[f]] <- deparse(temp_x$trial_spec[[f]])
    assign(x, temp_x)
  }

  expect_equal(res, loaded_res)
  expect_equal(res, res_with_progress)

  # Check that seed is unchanged
  expect_identical(oldseed, get(".Random.seed", envir = globalenv()))
})

test_that("prog_breaks", {
  expect_snapshot(prog_breaks(0.1, prev_n_rep = 10, n_rep_new = 20, cores = 1))
  expect_snapshot(prog_breaks(0.1, prev_n_rep = 0, n_rep_new = 10, cores = 2))
})

# This test also uses extract_results, to avoid the issue mentioned at the top
test_that("Multiple trials simulation works on multiple cores", {
  setup <- read_testdata("binom__setup__3_arms__no_control__equivalence__softened")
  res <- run_trials(setup, n_rep = 20, base_seed = 12345, sparse = FALSE)
  expect_snapshot(extract_results(res)) # Avoid empty test

  # Tests only run conditionally, see check_cluster_version() function for
  # explanation. This cluster is only used to check version of adaptr on the cluster
  cl <- parallel::makeCluster(2)
  on.exit(parallel::stopCluster(cl))

  if (check_cluster_version(cl, "1.0.0")) { # Any released version of adaptr installed
    # Run trials on multiple cores
    res_mc <- run_trials(setup, n_rep = 20, base_seed = 12345, sparse = FALSE, cores = 2)

    # Always test using extract_results to avoid issues mentioned in check_cluster_version()
    expect_equal(extract_results(res),
                 extract_results(res_mc))


    # Only test íf most updated version installed
    if (check_cluster_version(cl)) {
      # Harmonise items know to be problematic (run-time and functions)
      for (x in c("res", "res_mc")) {
        temp_x <- get(x)
        temp_x$elapsed_time <- as.difftime(0, units = "secs")
        for (f in c("fun_y_gen", "fun_draws", "fun_raw_est"))
          temp_x$trial_spec[[f]] <- deparse(temp_x$trial_spec[[f]])
        assign(x, temp_x)
      }
      expect_equal(res, res_mc)
    }
  }
})

test_that("run_trials errors on invalid input", {
  setup <- read_testdata("binom__setup__3_arms__no_control__equivalence__softened")
  expect_error(run_trials(setup, n_rep = 10, sparse = NA))
  expect_error(run_trials(list(), n_rep = 10))
  expect_error(run_trials(setup, n_rep = 1:10, cores = 0.5))

  res <- run_trials(setup, n_rep = 10, base_seed = 4131)
  temp_res_file <- tempfile()
  on.exit(try(rm(temp_res_file), silent = TRUE), add = TRUE, after = FALSE)

  # Error growing object from pseudo-previous version
  res_err <- res
  res_err$adaptr_version <- NULL
  saveRDS(object = res_err, file = temp_res_file)
  expect_error(run_trials(setup, n_rep = 10, path = temp_res_file, base_seed = 4131))

  # Error with trial spec
  res_err <- res
  res_err$trial_spec$control <- "A"
  saveRDS(object = res_err, file = temp_res_file)
  expect_error(run_trials(setup, n_rep = 10, path = temp_res_file, base_seed = 4131))

  # Error with both grow and overwrite being TRUE
  expect_error(run_trials(setup, n_rep = 10, path = temp_res_file, grow = TRUE, overwrite = TRUE))

  # Error with n_rep being smaller than previous and warning with equal and grow == TRUE
  saveRDS(object = res, file = temp_res_file)
  expect_error(run_trials(setup, n_rep = 8, path = temp_res_file, base_seed = 4131))
  expect_warning(run_trials(setup, n_rep = 10, path = temp_res_file, grow = TRUE, base_seed = 4131))

  # Error with sparse being different
  expect_error(run_trials(setup, n_rep = 10, path = temp_res_file, base_seed = 4131, sparse = FALSE))

  # Error with base_seed from previous version
  res_err <- res
  res_err$base_seed <- 1234
  saveRDS(object = res_err, file = temp_res_file)
  expect_error(run_trials(setup, n_rep = 10, path = temp_res_file, base_seed = 4131))

  # grow == TRUE and invalid file path
  expect_error(run_trials(setup, n_rep = 10, path = paste0(temp_res_file, ".error"), grow = TRUE))

  # Other other values
  expect_error(run_trials(setup, n_rep = 10, base_seed = 0.3))
  expect_error(run_trials(setup, n_rep = 10, base_seed = 1, progress = 10))
  expect_error(run_trials(setup, n_rep = 10, base_seed = 1, cores = 0:1))

})

test_that("Growing trial objects works", {
  setup <- setup_trial_binom(
    arms = c("Arm A", "Arm B", "Arm C"),
    true_ys = c(0.25, 0.20, 0.30),
    min_probs = rep(0.15, 3),
    data_looks = seq(from = 300, to = 2000, by = 100),
    equivalence_prob = 0.9,
    equivalence_diff = 0.05,
    soften_power = 0.5
  )

  # Everything run in one go
  res1 <- run_trials(setup, n_rep = 20, base_seed = 12345)

  # Run in two "batches", saving results in a file
  temp_res_file <- tempfile()
  on.exit(try(rm(temp_res_file), silent = TRUE), add = TRUE, after = FALSE)
  res2 <- run_trials(setup, n_rep = 10, base_seed = 12345, path = temp_res_file)
  # Grow with progress bar to test
  sink_file <- tempfile() # diverts progress bar to not distort test output
  on.exit(try(file.remove(sink_file)), add = TRUE, after = FALSE)
  sink(sink_file)
  res2 <- run_trials(setup, n_rep = 20, base_seed = 12345, path = temp_res_file, grow = TRUE, progress = 0.05)
  sink()

  # Reload without growing
  res3 <- run_trials(setup, n_rep = 20, base_seed = 12345, temp_res_file)

  for (s in c("res1", "res2", "res3")) {
    temp_s <- get(s)
    temp_s$elapsed_time <- as.difftime(0, units = "secs")
    for (f in c("fun_y_gen", "fun_draws", "fun_raw_est"))
      temp_s[[f]] <- deparse(temp_s[[f]])
    assign(s, temp_s)
  }

  expect_equal(res1, res2)
  expect_equal(res1, res3)
})

Try the adaptr package in your browser

Any scripts or data that you put into this service are public.

adaptr documentation built on May 29, 2024, 7:48 a.m.