Skip to content

Commit

Permalink
Add <epidemic> class, WIP #156
Browse files Browse the repository at this point in the history
  • Loading branch information
pratikunterwegs committed Jan 29, 2024
1 parent 218aecb commit c0d1344
Show file tree
Hide file tree
Showing 7 changed files with 357 additions and 0 deletions.
224 changes: 224 additions & 0 deletions R/epidemic_class.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# Helper function
#' Get model arguments as a list, including default arguments
#'
#' @description
#' Internal function intended to be called inside a call to `model_*_*()` to
#' capture values passed to function arguments, including default values.
#' @param f A function or a function name as a string.
#'
#' @keywords internal
.get_model_parameters <- function(f) {
# NOTE: match.call() does not capture default values
# get values in a frame one level up
mget(
names(formals(f)),
sys.frame(sys.nframe() - 1L)
)
}

#' Constructor for the `<epidemic>` class
#'
#' @param model_fn A string giving the name of the model function.
#' @param data A `<data.frame>` of the model output. Must have four columns
#' named "time", "demography_group", "compartment", and value.
#' @param parameters A named list of model parameters used in a model run. These
#' may of atomic or more complex types, such as `<intervention>`, or NULL for
#' unused, optional arguments.
#' @param hash A string for the SHA-1 remote hash associated with the installed
#' version of `{epidemics}`. For named release versions, this is a version
#' number with semantic meaning such as `"0.1.0"`. For versions installed from
#' the remote, this is the latest commit hash on the installation branch.
#' This is typically auto-generated by the external constructor [epidemic()].
#'
#' @return An object of the `<epidemic>` class.
#' @noRd
new_epidemic <- function(model_fn, data, parameters, hash) {
# create and return epidemic class
# input checking in external constructor
x <- list(
model_fn = model_fn,
hash = hash,
data = data,
parameters = parameters
)
class(x) <- c(class, "epidemic")

x
}

#' Create an `<epidemic>` object
#'
#' @description
#' A simple class to hold the output of epidemic models along with the model
#' parameters and any composable elements, such as interventions, applied.
#'
#' @param model_fn A string giving the name of the model function.
#' @param data A `<data.frame>` of the model output. Must have four columns
#' named "time", "demography_group", "compartment", and value.
#' @param parameters A named list of model parameters used in a model run. These
#' may of atomic or more complex types, such as `<intervention>`, or NULL for
#' unused, optional arguments.
#'
#' @name epidemic_class
#' @rdname epidemic_class
#'
#' @examples
#' # Run a model to get an <epidemic> class output
#' uk_population <- population(
#' name = "UK population",
#' contact_matrix = matrix(1),
#' demography_vector = 67e6,
#' initial_conditions = matrix(
#' c(0.9999, 0.0001, 0, 0, 0),
#' nrow = 1, ncol = 5L
#' )
#' )
#'
#' # run epidemic simulation with no vaccination or intervention
#' output <- model_default_cpp(
#' population = uk_population
#' )
#'
#' output
#'
#' # access parameters
#' get_parameter(output, "transmissibility")
#' @export
epidemic <- function(model_fn, data, parameters) {
# check input
checkmate::assert_string(model_fn)
checkmate::assert_data_frame(
data,
any.missing = FALSE, min.rows = 1, ncols = 4
)
# allow some missing values as interventions and vaccinations may be
# NULL by default
# DO NOT expect unique values as rates and NULLs may repeat
checkmate::assert_list(
parameters,
any.missing = TRUE, all.missing = FALSE, min.len = 1L,
names = "unique"
)

# get package version or hash
# NOTE: is NULL when installed from local version
hash <- utils::packageDescription("epidemics")[["RemoteSha"]]

# return epidemic class
new_epidemic(model_fn, data, parameters, hash)
}

#' Validate <epidemic> class objects
#'
#' @name validate_epidemic
#' @rdname validate_epidemic
#'
#' @param x An `<epidemic>` object.
#'
#' @return Invisibly returns the input `x`. Called primarily for its
#' input checking as a validator.
#' @keywords internal
validate_epidemic <- function(x) {
# check for class and class invariants
stopifnot(
"`x` should be of class <epidemic>" =
(is_epidemic(x)),
"<epidemic> does not contain the correct attributes" =
(c(
"data", "parameters"
) %in% attributes(x)$names)
)

# check epidemic class members
checkmate::assert_string(x[["model_fn"]])
checkmate::assert_data_frame(
x[["data"]],
any.missing = FALSE, min.rows = 1, ncols = 4
)
# allow some missing values as interventions and vaccinations may be
# NULL by default
# DO NOT expect unique values as rates and NULLs may repeat
checkmate::assert_list(
x[["parameters"]],
any.missing = TRUE, all.missing = FALSE, min.len = 1L,
names = "unique"
)
checkmate::assert_string(x[["hash"]], null.ok = TRUE)

# invisibly return x
invisible(x)
}

#' Check whether an object is an `<epidemic>`
#'
#' @param x An object to be checked as belonging to the `<epidemic>` class.
#'
#' @name epidemic
#' @rdname epidemic
#' @export
is_epidemic <- function(x) {
inherits(x, "epidemic")
}

#' Print an object of the `<epidemic>` class
#'
#' @name print_epidemic
#' @rdname print_epidemic
#'
#' @param x An `<epidemic>` class object.
#' @param ... Other parameters passed to [print()].
#' @return Invisibly returns the object `x`. Called for printing side-effects.
#' @export
print.epidemic <- function(x, ...) {
format(x, ...)
}

#' Format an `<epidemic>` object
#'
#' @param x A `<epidemic>` object.
#' @param ... Other arguments passed to [format()].
#'
#' @return Invisibly returns the [`<epidemic>`] object `x`.
#' Called for printing side-effects.
#' @keywords internal
#' @noRd
format.epidemic <- function(x, ...) {
validate_epidemic(x)

# NOTE: WORK IN PROGRESS
model_fn <- x[["model_fn"]]
model_fn <- glue::glue("Model function: {model_fn}")

compartments <- x[["parameters"]][["compartments"]]
compartments <- glue::glue_collapse(
glue::double_quote(compartments),
sep = ", "
)
compartments <- glue::glue("Compartments: {compartments}")
hash <- x[["hash"]]
if (is.null(hash)) {
hash <- "Local version"
}
hash <- glue::glue("{{epidemics}} version: {hash}")

# header
header <- "Head of model data:"
writeLines(
c(
"<epidemic>",
model_fn,
compartments,
hash,
header
)
)

print(head(x[["data"]]))

invisible(x)
}

# NOTE: an as.epidemic() method was considered and suspended pending questions
# around how to ensure sensible entries for the model function name and the hash
# as ideally it should NOT be possible for users to provide or modify these
# to guard against errors.
16 changes: 16 additions & 0 deletions man/dot-get_model_parameters.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions man/epidemic.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions man/epidemic_class.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions man/print_epidemic.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions man/validate_epidemic.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions tests/testthat/_snaps/epidemic_class.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# <epidemic> class basic expectations

Code
output
Output
<epidemic>
Model function: model_default_cpp
Compartments: "susceptible", "exposed", "infectious", "recovered", "vaccinated"
{epidemics} version: Local version
Head of model data:
time demography_group compartment value
1 0 [0,20) susceptible 14797810
2 0 [20,40) susceptible 16524649
3 0 40+ susceptible 28958263
4 0 [0,20) exposed 0
5 0 [20,40) exposed 0
6 0 40+ exposed 0

0 comments on commit c0d1344

Please sign in to comment.