diff --git a/R/epidemic_class.R b/R/epidemic_class.R new file mode 100644 index 00000000..7fd9b3ed --- /dev/null +++ b/R/epidemic_class.R @@ -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 `` class +#' +#' @param model_fn A string giving the name of the model function. +#' @param data A `` 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 ``, 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 `` 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 `` 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 `` 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 ``, or NULL for +#' unused, optional arguments. +#' +#' @name epidemic_class +#' @rdname epidemic_class +#' +#' @examples +#' # Run a model to get an 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 class objects +#' +#' @name validate_epidemic +#' @rdname validate_epidemic +#' +#' @param x An `` 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 " = + (is_epidemic(x)), + " 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 `` +#' +#' @param x An object to be checked as belonging to the `` class. +#' +#' @name epidemic +#' @rdname epidemic +#' @export +is_epidemic <- function(x) { + inherits(x, "epidemic") +} + +#' Print an object of the `` class +#' +#' @name print_epidemic +#' @rdname print_epidemic +#' +#' @param x An `` 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 `` object +#' +#' @param x A `` object. +#' @param ... Other arguments passed to [format()]. +#' +#' @return Invisibly returns the [``] 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( + "", + 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. diff --git a/man/dot-get_model_parameters.Rd b/man/dot-get_model_parameters.Rd new file mode 100644 index 00000000..89af6985 --- /dev/null +++ b/man/dot-get_model_parameters.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/epidemic_class.R +\name{.get_model_parameters} +\alias{.get_model_parameters} +\title{Get model arguments as a list, including default arguments} +\usage{ +.get_model_parameters(f) +} +\arguments{ +\item{f}{A function or a function name as a string.} +} +\description{ +Internal function intended to be called inside a call to \verb{model_*_*()} to +capture values passed to function arguments, including default values. +} +\keyword{internal} diff --git a/man/epidemic.Rd b/man/epidemic.Rd new file mode 100644 index 00000000..e0993f0e --- /dev/null +++ b/man/epidemic.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/epidemic_class.R +\name{epidemic} +\alias{epidemic} +\alias{is_epidemic} +\title{Check whether an object is an \verb{}} +\usage{ +is_epidemic(x) +} +\arguments{ +\item{x}{An object to be checked as belonging to the \verb{} class.} +} +\description{ +Check whether an object is an \verb{} +} diff --git a/man/epidemic_class.Rd b/man/epidemic_class.Rd new file mode 100644 index 00000000..0b997e8f --- /dev/null +++ b/man/epidemic_class.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/epidemic_class.R +\name{epidemic_class} +\alias{epidemic_class} +\alias{epidemic} +\title{Create an \verb{} object} +\usage{ +epidemic(model_fn, data, parameters) +} +\arguments{ +\item{model_fn}{A string giving the name of the model function.} + +\item{data}{A \verb{} of the model output. Must have four columns +named "time", "demography_group", "compartment", and value.} + +\item{parameters}{A named list of model parameters used in a model run. These +may of atomic or more complex types, such as \verb{}, or NULL for +unused, optional arguments.} +} +\description{ +A simple class to hold the output of epidemic models along with the model +parameters and any composable elements, such as interventions, applied. +} +\examples{ +# Run a model to get an 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") +} diff --git a/man/print_epidemic.Rd b/man/print_epidemic.Rd new file mode 100644 index 00000000..7dc4b902 --- /dev/null +++ b/man/print_epidemic.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/epidemic_class.R +\name{print_epidemic} +\alias{print_epidemic} +\alias{print.epidemic} +\title{Print an object of the \verb{} class} +\usage{ +\method{print}{epidemic}(x, ...) +} +\arguments{ +\item{x}{An \verb{} class object.} + +\item{...}{Other parameters passed to \code{\link[=print]{print()}}.} +} +\value{ +Invisibly returns the object \code{x}. Called for printing side-effects. +} +\description{ +Print an object of the \verb{} class +} diff --git a/man/validate_epidemic.Rd b/man/validate_epidemic.Rd new file mode 100644 index 00000000..8ca87b92 --- /dev/null +++ b/man/validate_epidemic.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/epidemic_class.R +\name{validate_epidemic} +\alias{validate_epidemic} +\title{Validate \if{html}{\out{}} class objects} +\usage{ +validate_epidemic(x) +} +\arguments{ +\item{x}{An \verb{} object.} +} +\value{ +Invisibly returns the input \code{x}. Called primarily for its +input checking as a validator. +} +\description{ +Validate \if{html}{\out{}} class objects +} +\keyword{internal} diff --git a/tests/testthat/_snaps/epidemic_class.md b/tests/testthat/_snaps/epidemic_class.md new file mode 100644 index 00000000..4ec6eed6 --- /dev/null +++ b/tests/testthat/_snaps/epidemic_class.md @@ -0,0 +1,18 @@ +# class basic expectations + + Code + output + Output + + 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 +