diff --git a/DESCRIPTION b/DESCRIPTION index d1caca5b..2273eb84 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -50,5 +50,5 @@ Config/testthat/edition: 3 Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.0 +RoxygenNote: 7.3.1 Language: en-GB diff --git a/R/checks.R b/R/checks.R index c4bc7060..55097a2f 100644 --- a/R/checks.R +++ b/R/checks.R @@ -18,3 +18,101 @@ x <- generation_time(10) checkmate::assert_numeric(x, len = 10) } + +#' Check inputs to `simulate_chains()` and `simulate_summary()` +#' +#' @param sim_func ; The simulation function to check +#' +#' @inheritParams simulate_chains +#' @return NULL; called for side effects +#' @keywords internal +.check_sim_args <- function( + index_cases, + statistic, + offspring_dist, + stat_max, + pop, + percent_immune) { + # Input checking + checkmate::assert_count( + index_cases, + positive = TRUE + ) + # check that offspring is a function with argument "n" + .check_offspring_func_valid(offspring_dist) + # check that arguments related to the statistic are valid + .check_statistic_args( + statistic, + stat_max + ) + checkmate::assert( + is.infinite(pop) || + checkmate::assert_integerish(pop, lower = 1) + ) + checkmate::assert_number( + percent_immune, + lower = 0, upper = 1 + ) + invisible(NULL) +} + +#' Check that the `statistic` and `stat_max` arguments are valid +#' +#' @inheritParams simulate_chains +#' @description +#' The function treats these two arguments as related and checks +#' them in one place to remove repeated checks in several places in the +#' package. +#' @return NULL; called for side effects +#' @keywords internal +.check_statistic_args <- function(statistic, + stat_max) { + checkmate::assert_choice( + statistic, + choices = c("size", "length") + ) + checkmate::assert( + is.infinite(stat_max), + checkmate::check_integerish( + stat_max, + lower = 1, + null.ok = FALSE + ), + combine = "or" + ) +} + +#' Check inputs that control time events +#' +#' @description +#' This function checks the time-related inputs, i.e., start time of each chain, +#' `t0`, the end time of the simulation, `tf`, and the generation time, +#' generation_time. It also checks that the generation_time argument is +#' specified if `tf` is specified as these go hand-in-hand. +#' +#' @param tf_specified ; Whether the `tf` argument is specified. Only +#' makes sense in the context where this function is called, i.e., in +#' [simulate_chains()]. If `tf` is specified, generation_time must be specified. +#' @inheritParams simulate_chains +#' @return NULL; called for side effects +#' @keywords internal +.check_time_args <- function(tf_specified, + tf, + generation_time, + t0) { + # if tf is specified, generation_time must be specified too + if (!is.null(generation_time)) { + .check_generation_time_valid(generation_time) + } else if (tf_specified) { + stop("If `tf` is specified, `generation_time` must be specified too.") + } + checkmate::assert_number( + tf, + lower = 0 + ) + checkmate::assert_numeric( + t0, + lower = 0, + finite = TRUE + ) +} diff --git a/R/helpers.R b/R/helpers.R index 4393cfcb..b613c564 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -35,6 +35,91 @@ #' Adjust next generation vector to match susceptible population size #' +#' @description Calculates the initial susceptible population size given +#' the total population size, the percent immune, and the number of index +#' cases. This function is used internally, and input checking is not +#' performed here, only in the context where it is used. Using it directly +#' is not recommended. +#' +#' @inheritParams simulate_chains +#' +#' @return numeric; initial susceptible population size +#' @keywords internal +.init_susc_pop <- function(pop, + percent_immune, + index_cases) { + ss <- max(round(pop * (1 - percent_immune)) - index_cases, 0) + return(ss) +} + +#' Sample all possible offspring for the next generation +#' +#' @description +#' Sample next generation of offspring using offspring distribution and +#' associated parameters. This function is used internally, and input +#' checking is not performed here, only in the context where it is used. +#' Using it directly is not recommended. +#' @param offspring_func A function to sample offspring +#' @param offspring_func_pars A list of parameters for the offspring function +#' @param n_offspring A vector of the number of offspring per chain +#' @param chains Indices of chains/infectors being simulated +#' +#' @return A vector of the number of offspring per chain +#' @keywords internal +.sample_possible_offspring <- function(offspring_func, + offspring_func_pars, + n_offspring, + chains) { + + possible_new_offspring <- do.call( + offspring_func, + c( + list(n = sum(n_offspring[chains])), + offspring_func_pars + ) + ) + # check that offspring distribution returns integers + stopifnot( + "Offspring distribution must return integers" = + !all(possible_new_offspring %% 1 > 0) + ) + + return(possible_new_offspring) +} + +#' Sample the number of susceptible offspring from all possible offspring +#' +#' @description +#' Sample susceptible offspring to be infected from all possible offspring. +#' This function is used internally, and input checking is not +#' performed here, only in the context where it is used. Using it directly +#' is not recommended. +#' @inheritParams simulate_chains +#' @param new_offspring A vector of the possible new offspring per chain +#' produced by [.sample_possible_offspring()] +#' @return A vector of the number of offspring that can be infected given the +#' current susceptible population size +#' @keywords internal +.get_susceptible_offspring <- function(new_offspring, + susc_pop, + pop) { + # We first adjust for the case where susceptible can be Inf but prob can only + # be maximum 1. + binom_prob <- min(1, susc_pop / pop, na.rm = TRUE) + # Sample the number of infectible offspring from all possible offspring + susceptible_offspring <- stats::rbinom( + n = length(new_offspring), + size = new_offspring, + prob = binom_prob + ) + return(susceptible_offspring) +} + +#' Adjust new offspring if it exceeds the susceptible population size +#' @description +#' This function is used internally, and input checking is not +#' performed here, only in the context where it is used. Using it directly +#' is not recommended. #' @param next_gen numeric; vector of next generation offspring #' @param susc_pop numeric; susceptible population size #' diff --git a/R/likelihood.R b/R/likelihood.R index 879d11d6..2d1668a4 100644 --- a/R/likelihood.R +++ b/R/likelihood.R @@ -53,18 +53,18 @@ likelihood <- function(chains, statistic = c("size", "length"), offspring_dist, nsim_obs, lower = 1, finite = TRUE, na.ok = FALSE ) } - checkmate::assert_numeric( chains, lower = 0, upper = Inf, any.missing = FALSE ) - checkmate::assert_character(statistic) + # check that arguments related to the statistic are valid + .check_statistic_args( + statistic, + stat_max + ) .check_offspring_func_valid(offspring_dist) checkmate::assert_number( obs_prob, lower = 0, upper = 1, finite = TRUE, na.ok = FALSE ) - checkmate::assert_number( - stat_max, lower = 0, na.ok = FALSE - ) checkmate::assert_logical( log, any.missing = FALSE, all.missing = FALSE, len = 1 ) @@ -74,7 +74,6 @@ likelihood <- function(chains, statistic = c("size", "length"), offspring_dist, checkmate::assert_numeric( exclude, null.ok = TRUE ) - if (obs_prob < 1) { if (missing(nsim_obs)) { stop("'nsim_obs' must be specified if 'obs_prob' is < 1") diff --git a/R/simulate.r b/R/simulate.r index ee4e08ea..d1317921 100644 --- a/R/simulate.r +++ b/R/simulate.r @@ -128,7 +128,6 @@ #' "Branching Process Models for Surveillance of Infectious Diseases #' Controlled by Mass Vaccination.” Biostatistics (Oxford, England) #' 4 (2): 279–95. \doi{https://doi.org/10.1093/biostatistics/4.2.279}. -# nolint start: cyclocomp_linter. simulate_chains <- function(index_cases, statistic = c("size", "length"), offspring_dist, @@ -139,40 +138,25 @@ simulate_chains <- function(index_cases, generation_time = NULL, t0 = 0, tf = Inf) { - # Input checking - checkmate::assert_count( - index_cases, - positive = TRUE - ) - checkmate::assert_choice( - statistic, - choices = c("size", "length") - ) - .check_offspring_func_valid(offspring_dist) - checkmate::assert( - is.infinite(stat_max) || - checkmate::assert_integerish(stat_max, lower = 0) - ) - checkmate::assert( - is.infinite(pop) || - checkmate::assert_integerish(pop, lower = 1) - ) - checkmate::assert_number( - percent_immune, - lower = 0, upper = 1 - ) - if (!missing(generation_time)) { - .check_generation_time_valid(generation_time) - } else if (!missing(tf)) { - stop("If `tf` is specified, `generation_time` must be specified too.") - } - checkmate::assert_numeric( - t0, - lower = 0, finite = TRUE + # Check offspring and population-related arguments + .check_sim_args( + index_cases = index_cases, + statistic = statistic, + offspring_dist = offspring_dist, + stat_max = stat_max, + pop = pop, + percent_immune = percent_immune ) - checkmate::assert_number( - tf, - lower = 0 + # Check time-related arguments + # Since tf is passed to .check_time_args, we need to check if it is specified + # in this function environment. If tf is specified, we expect generation_time + # to be specified too. + tf_specified <- !missing(tf) + .check_time_args( + generation_time = generation_time, + t0 = t0, + tf_specified = tf_specified, + tf = tf ) # Gather offspring distribution parameters pars <- list(...) @@ -194,7 +178,7 @@ simulate_chains <- function(index_cases, ) ) # Initialise susceptible population - susc_pop <- max(round(pop * (1 - percent_immune)) - index_cases, 0) + susc_pop <- .init_susc_pop(pop, percent_immune, index_cases) # Add optional columns if (!missing(generation_time)) { @@ -206,30 +190,20 @@ simulate_chains <- function(index_cases, } # next, simulate n trees while (length(sim) > 0 && susc_pop > 0) { - # sample next generation of offspring - next_gen <- do.call( - offspring_dist, - c( - list(n = sum(n_offspring[sim])), - pars - ) - ) - # check that offspring distribution returns integers - stopifnot( - "Offspring distribution must return integers" = - !all(next_gen %% 1 > 0) + # simulate the next possible offspring + next_gen <- .sample_possible_offspring( + offspring_func = offspring_dist, + offspring_func_pars = pars, + n_offspring = n_offspring, + chains = sim ) - # Sample susceptible offspring to be infected from all possible offspring - # We first adjust for the case where susceptible can be Inf but prob is max - # 1. - binom_prob <- min(1, susc_pop / pop, na.rm = TRUE) - next_gen <- stats::rbinom( - n = length(next_gen), - size = next_gen, - prob = binom_prob + # from all possible offspring, get those that could be infected + next_gen <- .get_susceptible_offspring( + new_offspring = next_gen, + susc_pop = susc_pop, + pop = pop ) - # Adjust next_gen if the number of offspring is greater than the - # susceptible population. + # Adjust the infectibles if they exceed the susceptible population if (sum(next_gen) > susc_pop) { next_gen <- .adjust_next_gen( next_gen = next_gen, @@ -322,7 +296,6 @@ simulate_chains <- function(index_cases, ) return(out) } -# nolint end #' Simulate a vector of transmission chains sizes/lengths #' @@ -393,27 +366,14 @@ simulate_summary <- function(index_cases, stat_max = Inf, pop = Inf, percent_immune = 0) { - # Input checking - checkmate::assert_count(index_cases, positive = TRUE) - statistic <- match.arg(statistic) - checkmate::assert_choice( - statistic, - choices = c("size", "length") - ) - - # check that offspring is properly specified - .check_offspring_func_valid(offspring_dist) - - checkmate::assert_number( - stat_max, lower = 0 - ) - checkmate::assert( - is.infinite(pop) || - checkmate::assert_integerish(pop, lower = 1) - ) - checkmate::assert_number( - percent_immune, - lower = 0, upper = 1 + # Check offspring and population-related arguments + .check_sim_args( + index_cases = index_cases, + statistic = statistic, + offspring_dist = offspring_dist, + stat_max = stat_max, + pop = pop, + percent_immune = percent_immune ) # Gather offspring distribution parameters pars <- list(...) @@ -424,39 +384,25 @@ simulate_summary <- function(index_cases, sim <- seq_len(index_cases) ## track trees that are still being simulated # Initialise susceptible population - susc_pop <- max(round(pop * (1 - percent_immune)) - index_cases, 0) + susc_pop <- .init_susc_pop(pop, percent_immune, index_cases) ## next, simulate transmission chains from index cases while (length(sim) > 0 && susc_pop > 0) { - ## simulate next generation - next_gen <- do.call( - offspring_dist, - c( - list(n = sum(n_offspring[sim])), - pars - ) + # simulate the possible next generation of offspring + next_gen <- .sample_possible_offspring( + offspring_func = offspring_dist, + offspring_func_pars = pars, + n_offspring = n_offspring, + chains = sim ) - # check that offspring distribution returns integers - stopifnot( - "Offspring distribution must return integers" = - !all(next_gen %% 1 > 0) - ) - # Sample susceptible offspring to be infected from all possible offspring - # We first adjust for the case where susceptible can be Inf but prob is max - # 1. - binom_prob <- min( - 1, - susc_pop / pop, - na.rm = TRUE - ) - - next_gen <- stats::rbinom( - n = length(next_gen), - size = next_gen, - prob = binom_prob + # from all possible offspring, get those that are infectible + next_gen <- .get_susceptible_offspring( + new_offspring = next_gen, + susc_pop = susc_pop, + pop = pop ) # Adjust next_gen if the number of offspring is greater than the - # susceptible population. + # susceptible population if (sum(next_gen) > susc_pop) { next_gen <- .adjust_next_gen( next_gen = next_gen, diff --git a/R/stat_likelihoods.R b/R/stat_likelihoods.R index 6581d79b..15368b34 100644 --- a/R/stat_likelihoods.R +++ b/R/stat_likelihoods.R @@ -169,13 +169,7 @@ offspring_ll <- function(x, offspring_dist, statistic, checkmate::assert_numeric( x, lower = 0, any.missing = FALSE ) - # check that offspring is properly specified - .check_offspring_func_valid(offspring_dist) - checkmate::assert_character(statistic) - checkmate::assert_numeric( - nsim_offspring, lower = 1 - ) - + # Remaining checks are done in simulate_summary() # Simulate the chains dist <- simulate_summary( index_cases = nsim_offspring, diff --git a/man/dot-adjust_next_gen.Rd b/man/dot-adjust_next_gen.Rd index f213cfc5..ae9af4fe 100644 --- a/man/dot-adjust_next_gen.Rd +++ b/man/dot-adjust_next_gen.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/helpers.R \name{.adjust_next_gen} \alias{.adjust_next_gen} -\title{Adjust next generation vector to match susceptible population size} +\title{Adjust new offspring if it exceeds the susceptible population size} \usage{ .adjust_next_gen(next_gen, susc_pop) } @@ -15,6 +15,8 @@ numeric; adjusted next generation offspring vector } \description{ -Adjust next generation vector to match susceptible population size +This function is used internally, and input checking is not +performed here, only in the context where it is used. Using it directly +is not recommended. } \keyword{internal} diff --git a/man/dot-check_sim_args.Rd b/man/dot-check_sim_args.Rd new file mode 100644 index 00000000..fd3172e2 --- /dev/null +++ b/man/dot-check_sim_args.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/checks.R +\name{.check_sim_args} +\alias{.check_sim_args} +\title{Check inputs to \code{simulate_chains()} and \code{simulate_summary()}} +\usage{ +.check_sim_args( + index_cases, + statistic, + offspring_dist, + stat_max, + pop, + percent_immune +) +} +\arguments{ +\item{index_cases}{Number of index cases to simulate transmission chains for.} + +\item{statistic}{\verb{}; Chain statistic to track as the stopping +criteria for each chain being simulated when \code{stat_max} is not \code{Inf}. +Can be one of: +\itemize{ +\item "size": the total number of cases produced by a chain before it goes +extinct. +\item "length": the total number of ancestors produced by a chain before +it goes extinct. +}} + +\item{offspring_dist}{Offspring distribution: a function like the ones +provided by R to generate random numbers from given distributions (e.g., +\code{\link{rpois}} for Poisson). More specifically, the function needs to +accept at least one argument, \code{n}, which is the number of random +numbers to generate. It can accept further arguments, which will be passed +on to the random number generating functions. Examples that can be provided +here are \code{rpois} for Poisson distributed offspring, \code{rnbinom} for negative +binomial offspring, or custom functions.} + +\item{stat_max}{A cut off for the chain statistic (size/length) being +computed. Results above \code{stat_max} are set to \code{stat_max}. Defaults to \code{Inf}.} + +\item{pop}{\verb{}; Population size. Used alongside \code{percent_immune}. to +define the susceptible population. Defaults to \code{Inf}.} + +\item{percent_immune}{\verb{}; Percent of the population immune to +infection at the start of the simulation. Used alongside \code{pop} to initialise +the susceptible population. Accepted values lie between 0 and 1. +Defaults to 0.} + +\item{sim_func}{\if{html}{\out{}}; The simulation function to check} +} +\value{ +NULL; called for side effects +} +\description{ +Check inputs to \code{simulate_chains()} and \code{simulate_summary()} +} +\keyword{internal} diff --git a/man/dot-check_statistic_args.Rd b/man/dot-check_statistic_args.Rd new file mode 100644 index 00000000..52ba7ea2 --- /dev/null +++ b/man/dot-check_statistic_args.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/checks.R +\name{.check_statistic_args} +\alias{.check_statistic_args} +\title{Check that the \code{statistic} and \code{stat_max} arguments are valid} +\usage{ +.check_statistic_args(statistic, stat_max) +} +\arguments{ +\item{statistic}{\verb{}; Chain statistic to track as the stopping +criteria for each chain being simulated when \code{stat_max} is not \code{Inf}. +Can be one of: +\itemize{ +\item "size": the total number of cases produced by a chain before it goes +extinct. +\item "length": the total number of ancestors produced by a chain before +it goes extinct. +}} + +\item{stat_max}{A cut off for the chain statistic (size/length) being +computed. Results above \code{stat_max} are set to \code{stat_max}. Defaults to \code{Inf}.} +} +\value{ +NULL; called for side effects +} +\description{ +The function treats these two arguments as related and checks +them in one place to remove repeated checks in several places in the +package. +} +\keyword{internal} diff --git a/man/dot-check_time_args.Rd b/man/dot-check_time_args.Rd new file mode 100644 index 00000000..6c6c31a7 --- /dev/null +++ b/man/dot-check_time_args.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/checks.R +\name{.check_time_args} +\alias{.check_time_args} +\title{Check inputs that control time events} +\usage{ +.check_time_args(tf_specified, tf, generation_time, t0) +} +\arguments{ +\item{tf_specified}{\if{html}{\out{}}; Whether the \code{tf} argument is specified. Only +makes sense in the context where this function is called, i.e., in +\code{\link[=simulate_chains]{simulate_chains()}}. If \code{tf} is specified, generation_time must be specified.} + +\item{tf}{Cut-off for the infection times (if generation time is given). +Defaults to \code{Inf}.} + +\item{generation_time}{The generation time function; the name +of a user-defined named or anonymous function with only one argument \code{n}, +representing the number of generation times to sample.} + +\item{t0}{Start time (if generation time is given); either a single value +or a vector of same length as \code{index_cases} (number of initial cases) with +corresponding initial times. Defaults to 0, meaning all cases started at +time 0.} +} +\value{ +NULL; called for side effects +} +\description{ +This function checks the time-related inputs, i.e., start time of each chain, +\code{t0}, the end time of the simulation, \code{tf}, and the generation time, +generation_time. It also checks that the generation_time argument is +specified if \code{tf} is specified as these go hand-in-hand. +} +\keyword{internal} diff --git a/man/dot-get_susceptible_offspring.Rd b/man/dot-get_susceptible_offspring.Rd new file mode 100644 index 00000000..b67f14d3 --- /dev/null +++ b/man/dot-get_susceptible_offspring.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/helpers.R +\name{.get_susceptible_offspring} +\alias{.get_susceptible_offspring} +\title{Sample the number of susceptible offspring from all possible offspring} +\usage{ +.get_susceptible_offspring(new_offspring, susc_pop, pop) +} +\arguments{ +\item{new_offspring}{A vector of the possible new offspring per chain +produced by \code{\link[=.sample_possible_offspring]{.sample_possible_offspring()}}} + +\item{pop}{\verb{}; Population size. Used alongside \code{percent_immune}. to +define the susceptible population. Defaults to \code{Inf}.} +} +\value{ +A vector of the number of offspring that can be infected given the +current susceptible population size +} +\description{ +Sample susceptible offspring to be infected from all possible offspring. +This function is used internally, and input checking is not +performed here, only in the context where it is used. Using it directly +is not recommended. +} +\keyword{internal} diff --git a/man/dot-init_susc_pop.Rd b/man/dot-init_susc_pop.Rd new file mode 100644 index 00000000..502b4aa9 --- /dev/null +++ b/man/dot-init_susc_pop.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/helpers.R +\name{.init_susc_pop} +\alias{.init_susc_pop} +\title{Adjust next generation vector to match susceptible population size} +\usage{ +.init_susc_pop(pop, percent_immune, index_cases) +} +\arguments{ +\item{pop}{\verb{}; Population size. Used alongside \code{percent_immune}. to +define the susceptible population. Defaults to \code{Inf}.} + +\item{percent_immune}{\verb{}; Percent of the population immune to +infection at the start of the simulation. Used alongside \code{pop} to initialise +the susceptible population. Accepted values lie between 0 and 1. +Defaults to 0.} + +\item{index_cases}{Number of index cases to simulate transmission chains for.} +} +\value{ +numeric; initial susceptible population size +} +\description{ +Calculates the initial susceptible population size given +the total population size, the percent immune, and the number of index +cases. This function is used internally, and input checking is not +performed here, only in the context where it is used. Using it directly +is not recommended. +} +\keyword{internal} diff --git a/man/dot-sample_possible_offspring.Rd b/man/dot-sample_possible_offspring.Rd new file mode 100644 index 00000000..3aa60b67 --- /dev/null +++ b/man/dot-sample_possible_offspring.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/helpers.R +\name{.sample_possible_offspring} +\alias{.sample_possible_offspring} +\title{Sample all possible offspring for the next generation} +\usage{ +.sample_possible_offspring( + offspring_func, + offspring_func_pars, + n_offspring, + chains +) +} +\arguments{ +\item{offspring_func}{A function to sample offspring} + +\item{offspring_func_pars}{A list of parameters for the offspring function} + +\item{n_offspring}{A vector of the number of offspring per chain} + +\item{chains}{Indices of chains/infectors being simulated} +} +\value{ +A vector of the number of offspring per chain +} +\description{ +Sample next generation of offspring using offspring distribution and +associated parameters. This function is used internally, and input +checking is not performed here, only in the context where it is used. +Using it directly is not recommended. +} +\keyword{internal} diff --git a/tests/testthat/test-checks.R b/tests/testthat/test-checks.R index 9871a94d..9e234c94 100644 --- a/tests/testthat/test-checks.R +++ b/tests/testthat/test-checks.R @@ -1,18 +1,169 @@ -test_that("Checks work", { - expect_error( - .check_offspring_func_valid(rrpois), - "not found" - ) - expect_error( - .check_generation_time_valid("a"), - "Must be a function" - ) - expect_error( - .check_generation_time_valid(function(x) rep("a", 10)), - "numeric" - ) - expect_error( - .check_generation_time_valid(function(x) 3), - "Must have length" - ) +# A function that passes all checks in .check_sim_args but returns an error +# message if the supplied arguments are invalid +.check_sim_args_default <- function(...) { + default_args <- list( + index_cases = 10, + statistic = "size", + offspring_dist = rpois, + stat_max = 10, + pop = 10, + percent_immune = 0.1 + ) + # Modify the default arguments with the user's arguments + new_args <- modifyList( + default_args, + list(...) + ) + # Run the check_sim_args function and capture the output + out <- tryCatch( + expr = { + do.call( + .check_sim_args, + new_args + ) + }, + error = function(e) { + stop(e) + } + ) + # Return the output + return(out) +} + +# A function that passes all checks in `.check_time_args` but returns an error +# message if the supplied arguments are invalid +.check_time_args_default <- function(...) { + default_args <- list( + t0 = 0, + tf_specified = FALSE, # tf is not specified but default tf = Inf below + tf = Inf, + generation_time = NULL + ) + new_args <- modifyList( + default_args, + list(...) + ) + out <- tryCatch( + expr = { + do.call( + .check_time_args, + new_args + ) + }, + error = function(e) { + stop(e) + } + ) + return(out) +} + + +test_that("Smaller checker functions work", { + expect_error( + .check_offspring_func_valid(rrpois), + "not found" + ) + expect_error( + .check_generation_time_valid("a"), + "Must be a function" + ) + expect_error( + .check_generation_time_valid(function(x) rep("a", 10)), + "numeric" + ) + expect_error( + .check_generation_time_valid(function(x) 3), + "Must have length" + ) + expect_no_error( + .check_statistic_args( + statistic = "size", + stat_max = 10 + ) + ) +}) + +test_that(".check_sim_args() returns errors", { + # Checks with .check_sim_args + expect_no_error( + .check_sim_args_default() + ) + # index_cases must be >= 1 + expect_error( + .check_sim_args_default( + index_cases = 0 + ), + "Must be >= 1." + ) + # statistic can only be "size" or "length" + expect_error( + .check_sim_args_default( + statistic = "duration" + ), + "Must be element of set \\{'size','length'\\}" + ) + # offspring_dist must be a function + expect_error( + .check_sim_args_default( + offspring_dist = "rpois" + ), + "Must be a function, not 'character'" + ) + # offspring_dist must be a known function (in the environment) + expect_error( + .check_sim_args_default( + offspring_dist = r + ), + "object 'r' not found" + ) + # stat_max must be >= 1 + expect_error( + .check_sim_args_default( + stat_max = 0 + ), + "Assertion failed." + ) + # pop cannot be negative + expect_error( + .check_sim_args_default( + pop = -1 + ), + "Element 1 is not >= 1." + ) + # percent_immune must be in between 0 and 1 + expect_error( + .check_sim_args_default( + percent_immune = 1.1 + ), + "Element 1 is not <= 1." + ) +}) + +test_that(".check_time_args() returns errors", { + # Checks with .check_time_args + expect_no_error( + .check_time_args_default() + ) + # t0 cannot be negative + expect_error( + .check_time_args_default( + t0 = -1 + ), + "Element 1 is not >= 0." + ) + # tf cannot be negative + expect_error( + .check_time_args_default( + tf = -1 + ), + "Element 1 is not >= 0." + ) + # If tf is specified, generation_time must be specified too + expect_error( + .check_time_args_default( + tf_specified = TRUE, + tf = 10 + ), + "If `tf` is specified, `generation_time` must be specified too." + ) }) diff --git a/tests/testthat/test-helpers.R b/tests/testthat/test-helpers.R index c7c05e86..0370478c 100644 --- a/tests/testthat/test-helpers.R +++ b/tests/testthat/test-helpers.R @@ -41,3 +41,87 @@ test_that("get_statistic_func works correctly", { "chain_statistic must be 'size' or 'length'" ) }) + +test_that(".init_susc_pop works correctly", { + expect_identical( + .init_susc_pop( + pop = 10, + percent_immune = 0.5, + index_cases = 1 + ), + 4 + ) + expect_identical( + .init_susc_pop( + pop = 0, + percent_immune = 0.5, + index_cases = 1 + ), + 0 + ) + expect_identical( + .init_susc_pop( + pop = 10, + percent_immune = 0, + index_cases = 0 + ), + 10 + ) + expect_length( + .init_susc_pop( + pop = 10, + percent_immune = 0, + index_cases = 1 + ), + 1 + ) + expect_type( + .init_susc_pop( + pop = 10, + percent_immune = 0, + index_cases = 1 + ), + "double" + ) +}) + +test_that(".init_susc_pop works correctly", { + expect_length( + .sample_possible_offspring( + offspring_func = "rpois", + offspring_func_pars = list(lambda = 1), + n_offspring = 10, + chains = 1 + ), 10 + ) + expect_error( + .sample_possible_offspring( + offspring_func = "rnorm", + offspring_func_pars = list(mean = 0, sd = 1), + n_offspring = 10, + chains = 1 + ), + "Offspring distribution must return integers" + ) +}) + +test_that(".init_susc_pop works correctly", { + next_gen <- c(1, 2, 5) + expect_length( + .get_susceptible_offspring( + new_offspring = next_gen, + susc_pop = 1, + pop = 20 + ), + length(next_gen) + ) + # If the susceptible population in infinite, next_gen should be returned + expect_identical( + .get_susceptible_offspring( + new_offspring = next_gen, + susc_pop = Inf, + pop = Inf + ), + as.integer(next_gen) + ) +}) diff --git a/vignettes/interventions.Rmd b/vignettes/interventions.Rmd index a667913f..4bdc701d 100644 --- a/vignettes/interventions.Rmd +++ b/vignettes/interventions.Rmd @@ -46,7 +46,7 @@ We simulate 200 chains tracking up to 99 infections: ```{r simulate_chains} sims <- simulate_summary( index_cases = 200, offspring_dist = rnbinom, stat_max = 99, mu = 1.2, - size = 0.5 + size = 0.5, statistic = "size" ) ``` @@ -72,7 +72,7 @@ For example, to reduce R by 25% at the population level we scale the `mu` parame ```{r simulate_chains_pop_control} sims <- simulate_summary( index_cases = 200, offspring_dist = rnbinom, stat_max = 99, mu = 0.9, - size = 0.5 + size = 0.5, statistic = "size" ) sims[is.infinite(sims)] <- 100 # Replace infections > 99 with 100 for plotting. ggplot(data.frame(x = sims), aes(x = x)) + @@ -110,7 +110,7 @@ Having defined this, we can generate simulations as before: ```{r simulate_chains_ind_control} sims <- simulate_summary( index_cases = 200, offspring_dist = rnbinom_ind, stat_max = 99, mu = 1.2, - size = 0.5, control = 0.25 + size = 0.5, control = 0.25, statistic = "size" ) sims[is.infinite(sims)] <- 100 # Replace infections > 99 with 100 for plotting. ggplot(data.frame(x = sims), aes(x = x)) + @@ -140,7 +140,7 @@ This can be likened to a disease control strategy where gatherings are limited t ```{r simulate_chains_truncated} sims <- simulate_summary( index_cases = 200, offspring_dist = rnbinom_truncated, stat_max = 99, - mu = 1.2, size = 0.5, max = 10 + mu = 1.2, size = 0.5, max = 10, statistic = "size" ) sims[is.infinite(sims)] <- 100 # Replace infections > 99 with 100 for plotting. ggplot(data.frame(x = sims), aes(x = x)) +