Diff of /R/custom_likelihoods.R [000000] .. [dfe06d]

Switch to side-by-side view

--- a
+++ b/R/custom_likelihoods.R
@@ -0,0 +1,218 @@
+#'
+#' Customise likelihood functions for outbreaker
+#'
+#' This function is used to specify customised likelihood functions for
+#' outbreaker. Custom functions are specified as a named list or series of
+#' comma-separated, named arguments, indicating which log-likelihood component
+#' they compute. Values currently available are:
+#'
+#' \itemize{
+#'
+#' \item \code{genetic}: the genetic likelihood; by default, the function
+#' \code{cpp_ll_genetic} is used.
+#'
+#' \item \code{timing_sampling}: the likelihood of sampling times; by default, the function
+#' \code{cpp_ll_timing_sampling} is used.
+#'
+#' \item \code{timing_infections}: the likelihood of infection times; by default, the function
+#' \code{cpp_ll_timing_infections} is used.
+#'
+#' \item \code{reporting}: the likelihood of the reporting process; by default,
+#' the function \code{cpp_ll_reporting} is used.
+#'
+#' \item \code{contact}: the likelihood of the contact tracing data; by default,
+#' the function \code{cpp_ll_contact} is used.
+#' }
+#'
+#' All log-likelihood functions should have the following arguments, in this
+#' order:
+#'
+#' \itemize{
+#'
+#' \item \code{data}: a list of named items containing input data as returned by
+#' \code{\link{outbreaker_data}}.
+#'
+#' \item \code{param}: a list of parameters with the class
+#' \code{\link{create_param}}.
+#'
+#' }
+#'
+#'
+#' @return
+#' A named list of list(function, arity) pairs with the class
+#'     \code{custom_likelihood}, each function implementing a customised
+#'     log-likelihood component of outbreaker. Functions which are not
+#'     customised will result in a list(NULL, 0) component. Any function with
+#'     arity 3 must have the third parameter default to NULL.
+#'
+#' @author Thibaut Jombart (\email{thibautjombart@@gmail.com}).
+#'
+#' @param ... a named list of functions, each computing a log-likelihood component.
+#'
+#' @return a list of named functions
+#'
+#' @seealso See \href{http://www.repidemicsconsortium.org/outbreaker2/articles/customisation.html#customizing-likelihood}{customization vignette} for detailed examples on how to customize likelihoods.
+#'
+#' @export
+#'
+#' @examples
+#'
+#' ## specify a null model by disabling all likelihood components
+#' f_null <- function(data, param) {
+#'   return(0.0)
+#' }
+#'
+#' null_model <- custom_likelihoods(genetic = f_null,
+#'                                 timing_sampling = f_null,
+#'                                 timing_infections = f_null,
+#'                                 reporting = f_null,
+#'                                 contact = f_null)
+#'
+#' null_config <- list(find_import = FALSE,
+#'                     n_iter = 100,
+#'                     sample_every = 1)
+#'
+#' ## load data
+#' x <- fake_outbreak
+#' data <- outbreaker_data(dates = x$sample, dna = x$dna, w_dens = x$w)
+#'
+#' res_null <- outbreaker(data = data,
+#'                        config = null_config,
+#'                        likelihoods = null_model)
+#'
+#' ## visualise ancestries to see if all transmission trees have been explored
+#' plot(res_null, type = "alpha")
+
+
+## USING CUSTOM LIKELIHOOD FUNCTIONS
+
+## Likelihood functions in outbreaker2 are implemented using Rcpp. However,
+## these functions can also be replaced by customized functions. These can be
+## specified by the user, through the '...' argument of
+## 'custom_likelihoods'. These functions must have at least 2 arguments:
+
+## - data: a valid 'outbreaker_data' list
+
+## - param: a list containing current parameter states, as returned by
+## - create_param
+
+## - [i=NULL]: (optional) a list of the cases for which the loglikelihoods
+## - should be calculated. Needs to default to `NULL` in which case the
+## - loglikelihood of the entire tree is calculated.
+
+custom_likelihoods <- function(...) {
+
+    ll_functions <- list(...)
+
+    if (length(ll_functions) == 1L && is.list(ll_functions[[1]])) {
+        ll_functions <- ll_functions[[1]]
+    }
+
+
+    defaults <- list(genetic = NULL,
+                     reporting = NULL,
+                     timing_infections = NULL,
+                     timing_sampling = NULL,
+                     contact = NULL
+                     )
+
+    likelihoods <-  modify_defaults(defaults, ll_functions, FALSE)
+    likelihoods_names <- names(likelihoods)
+
+
+
+    ## check all likelihoods are functions
+
+    function_or_null <- function(x) {
+        is.null(x) || is.function(x)
+    }
+    list_function_or_null <- function(x) {
+        is.list(x) && (is.null(x[[1]]) || is.function(x[[1]]))
+    }
+
+    # Ensure that custom_likelihoods(l) == custom_likelihoods(custom_likelihoods(l))
+    is_list_function_or_null <- vapply(likelihoods, list_function_or_null, logical(1))
+    is_function_or_null <- vapply(likelihoods, function_or_null, logical(1))
+
+    if (!all(is_function_or_null) & !all(is_list_function_or_null)) {
+        culprits <- likelihoods_names[!is_function_or_null]
+        msg <- paste0("The following likelihoods are not functions: ",
+                      paste(culprits, collapse = ", "))
+        stop(msg)
+    }
+
+    # If the arity of the likelihood functions is three, the last argument should
+    # be the (1-based) indices of the cases we're currently perturbing. This
+    # allows us to calculate the local likelihood delta, rather than having to
+    # calculate the likelihood of the entire tree twice for every single
+    # perturbation we make.
+    if (!all(is_list_function_or_null)) {
+      likelihoods <- lapply(
+        likelihoods,
+        function(x) {
+          if (is.null(x)) return(list(x, 0)); list(x, length(methods::formalArgs(x)))
+        }
+      )
+    }
+
+    arity_two_or_three <- function(x) {
+        if (is.function(x[[1]])) {
+            return (x[[2]] == 2L | x[[2]] == 3L)
+        }
+        return(T)
+    }
+
+    legal_arity <- vapply(likelihoods, arity_two_or_three, logical(1))
+
+    if (!all(legal_arity)) {
+        culprits <- likelihoods_names[!legal_arity]
+        msg <- paste0("The following likelihoods do not have arity two or three: ",
+                      paste(culprits, collapse=", "))
+        stop(msg)
+    }
+
+    names(likelihoods) <- likelihoods_names
+    class(likelihoods) <- c("custom_likelihoods", "list")
+    return(likelihoods)
+}
+
+
+
+
+
+
+
+#' @rdname custom_likelihoods
+#'
+#' @export
+#'
+#' @aliases print.custom_likelihoods
+#'
+#' @param x an \code{outbreaker_config} object as returned by \code{create_config}.
+#'
+
+print.custom_likelihoods <- function(x, ...) {
+    cat("\n\n ///// outbreaker custom likelihoods ///\n")
+    cat("\nclass:", class(x))
+    cat("\nnumber of items:", length(x), "\n\n")
+
+    is_custom <- !vapply(x, is.null, FALSE)
+
+
+    names_default <- names(x)[!is_custom]
+    if (length(names_default) > 0) {
+        cat("/// custom likelihoods set to NULL (default used) //\n")
+        print(x[!is_custom])
+    }
+
+
+    names_custom <- names(x)[is_custom]
+    if (length(names_custom) > 0) {
+        cat("/// custom likelihoods //\n")
+        print(x[is_custom])
+    }
+
+    return(invisible(NULL))
+
+}
+