R/utils.R

Defines functions delete_built_in_proposal cached_proposal_info save_builtin_proposal free_cache_cnum_c cache_user_proposal_c cache_proposal_c built_in_pars_trans get_buildin_sampling_function proposal_check_symmetric is_valid_proposal proposal_error_checking_and_preparation truncate_error_checking check_proposal_opt_criteria adjust_modes fix_function create_function

Documented in delete_built_in_proposal

#' @noRd
create_function <- function(fun, arg_list) {
  fun_args <- names(formals(fun))

  arg_list <- arg_list[names(arg_list) %in% fun_args]

  build_closure <- function(fun, ...) {
    return(function(x) {
      fun(x, ...)
    })
  }

  f <- do.call("build_closure", c(fun, arg_list))

  return(f)
}



#' @noRd
fix_function <- function(func) {
  func_env <- environment(func)
  func_body <- body(func)
  func_args <- names(formals(func))

  if (length(func_args) != 1) {
    cli::cli_abort(c("x" = "All provided functions must have exactly one parameter",
                     "i" = "Your function has {length(func_args)} parameters")
    )
  }

  # Remove parameters from the environment to exclude them from substitution
  captured_env <- as.list(func_env)
  captured_env <- captured_env[!names(captured_env) %in% func_args]

  # Replace free variables in the body with their values from the environment
  fixed_body <- eval(substitute(
    substitute(func_body, captured_env),
    list(func_body = func_body)
  ))

  # Create a new function with the modified body
  new_func <- func
  body(new_func) <- fixed_body
  environment(new_func) <- baseenv()  # Reset the environment to avoid referencing external variables

  new_func
}


#' @noRd
adjust_modes <- function(mode, xl, xr, f) {
  delta <- 0.0001
  new_modes <- mode[mode >= xl & mode <= xr]

  if (length(new_modes) == 0) {
    if (all(mode <= xl)) {
      return(xl)

    } else if (all(mode >= xr)) {
      return(xr)

    } else {
      if (f(xl) > f(xl + delta)) {
        new_modes <- c(new_modes, xl)
      }
      if (f(xr) > f(xr - delta)) {
        new_modes <- c(new_modes, xr)
      }
      return(new_modes)

    }
  } else {
    return(new_modes)
  }

}

#' @noRd
check_proposal_opt_criteria <- function(symmetric, grid_type, dendata) {
  if (dendata$scalable) {
    symmetric <- !is.null(symmetric)
    cnum <- dendata$c_num

    scaled_params <- cached_proposal_info(cnum)

    custom_params <- cached_proposal_info(cnum + 1)

    if (!is.null(scaled_params[1]) &&
        (scaled_params[1] != symmetric) &&
        identical(grid_type, "custom")) {
      cli::cli_abort(c("x" = "You need to delete the {.strong {if (scaled_params[1]) 'symmetric' else 'non-symmetric'}} scaled built-in proposal first.",
                       "i" = "To delete it, run: {.run delete_built_in_proposal(\"{dendata$name}\", \"scaled\")}."),
                     call = rlang::caller_env(),
                     .frame = rlang::caller_env())
    }

    if (!is.null(custom_params[1]) &&
        (custom_params[1] != symmetric) &&
        identical(grid_type, "scaled")) {
      cli::cli_abort(c("x" = "You need to delete the {.strong {if (custom_params[1]) 'symmetric' else 'non-symmetric'}} custom built-in proposal first.",
          "i" = "To delete it, run: {.run delete_built_in_proposal(\"{dendata$name}\", \"custom\")}."
        ),
        call = rlang::caller_env(),
        .frame = rlang::caller_env()
      )
    }
  }
}

#' @noRd
truncate_error_checking <- function(xl, xr, density) {
  if (!(is.numeric(xl) && length(xl) == 1)) {
    cli::cli_abort("{.strong xl} must be a scalar numeric value.")
  }

  if (!(is.numeric(xr) && length(xr) == 1)) {
    cli::cli_abort(c("x" = "{.strong xr} must be a scalar numeric value."))
  }

  if (xl >= xr) {
    cli::cli_abort(c("x" = "{.strong xl} must be smaller than {.strong xr}",
                     "i" = "You provided: xl = {xl}, xr = {xr}."))
  }

  if (xl < density$lower) {
    cli::cli_abort(c("x" = "{.strong xl} must be greater than or equal to the density lower bound ({density$lower}}).",
                     "i" = "You provided: xl = {xl}}."))
  }

  if (xr > density$upper) {
    cli::cli_abort(c("x" = "{.strong xr} must be smaller than or equal to the density upper bound ({density$upper}})",
                     "i" = "You provided: xr = {xr}}."))
  }


  return(list(xl = xl, xr = xr))

}

#' @noRd
proposal_error_checking_and_preparation <- function(gp) {
  modes <- gp$target$modes
  f <- gp$target$density
  between_minima <- gp$target$between_minima
  steps <- gp$proposal$steps
  theta <- gp$proposal$pre_acceptance_threshold
  proposal_range <- gp$proposal$proposal_range
  lower <- gp$target$left_bound
  upper <- gp$target$right_bound
  if (is.null(modes)) {
    cli::cli_abort(c("x" = "{.strong modes} must be provided",
                     "i" = "Ensure you specify the density modes."))
  }

  if (!is.function(f)) {
    cli::cli_abort(c("x" = "{.strong f} must be a valid density function."))
  }

  if (!is.null(steps) && steps < 1) {
    cli::cli_abort(c("x" = "{.strong steps} must be greater than or equal to {1}",
                     "i" = "You provided: steps = {steps}."))
  }
  if (!is.numeric(theta) || theta < 0.1 || theta > 1) {
    cli::cli_abort(c("x" = "{.strong theta} must be a numeric value in the range [{0.1}, {1}]",
                     "i" = "You provided theta = {theta}"))
  }

  if (!is.null(proposal_range)) {
    if (length(proposal_range) != 2) {
      cli::cli_abort(c("x" = "{.strong proposal_range} must be a vector of two elements",
                       "i" = "You provided: {length(proposal_range)} elements"))
    }

    if (proposal_range[1] < lower || proposal_range[2] > upper) {
      cli::cli_abort(c("x" = "{.strong proposal_range} must be within the distribution bounds [{lower}, {upper}]",
                       "i" = "You provided: proposal_range = [{proposal_range[1]}, {proposal_range[2]}]"))
    }

    if (proposal_range[1] > modes[1] ||
        proposal_range[2] < modes[length(modes)]) {
      cli::cli_abort(c("x" = "{.strong proposal_range} must include the distribution's modes",
                       "i" = "Provided range: [{proposal_range[1]}, {proposal_range[2]}]",
                       "i" =  "First mode: {modes[1]}, Last mode: {modes[length(modes)]}"))
    }
  } else {
    proposal_range <- gp$proposal_range <- c(lower, upper)
  }

  if (!is.null(between_minima)) {
    if (between_minima < lower || between_minima > upper) {
      cli::cli_abort(c("x" = "{.strong between_minima} must be within the distribution bounds [{lower}, {upper}]",
                       "i" = "You provided: between_minima = {between_minima}."))
    }

    if (length(between_minima) != (length(modes) - 1)) {
      cli::cli_abort(c("x" =  "{.strong between_minima} must have exactly {length(modes) - 1} elements",
                       "i" = "You provided: {length(between_minima)} elements"))
    }

    minima_len <- length(between_minima)

    for (i in seq_len(minima_len)) {
      if (!(between_minima[i] > modes[i] &&
            between_minima[i] < modes[i + 1])) {
        cli::cli_abort(c("x" = "{.strong between_minima} must be positioned between the distribution's modes",
                         "i" = "Issue found at index {i}: {between_minima[i]} is not between {modes[i]} and {modes[i + 1]}"))
      }
    }
  }

  gp$proposal$proposal_range <- proposal_range
  gp$proposal$pre_acceptance_threshold <- theta

  return(gp)
}

#' @import digest digest
#' @noRd
is_valid_proposal <- function(proposal) {
  if ("lock" %in% names(proposal)) {
    temp <- proposal[setdiff(names(proposal), "lock")]
    key <- digest(temp)

    if (key == proposal$lock) {
      return(TRUE)

    }

  }

  return(FALSE)
}


#' @noRd
proposal_check_symmetric <- function(gp) {
  if (!is.null(gp$target$symmetric)) {
    proposal_range <- gp$proposal$proposal_range
    center <- gp$target$modes

    gp$target$left_bound <- center
    gp$proposal$proposal_range <- c(center, proposal_range[2])

  }

  return(gp)

}



#' @noRd
get_buildin_sampling_function <- function(cnum, name) {
  if (cnum %% 2 == 0) {
    even <- TRUE
    cnum_search <- cnum - 1
  } else {
    even <- FALSE
    cnum_search <- cnum
  }



  if (built_in_proposals[[name]]$c_num == cnum_search) {
    if (even) {
      fun <- function(n) {
        args <- list(n = n)
        return(do.call(paste0(name, "_custom"), args))
      }

    } else {
      fun <- function(n) {
        args <- as.list(c(n = n, built_in_proposals[[name]]$std_params))
        do.call(paste0(name), args)
      }
    }
  } else {
    cli::cli_abort(c("x" = "{.strong Check the proposal caching number c_num}"))
  }

  return(fun)


}


#' @noRd
built_in_pars_trans <- function(f_params, cnum) {
  for (name in names(built_in_proposals)) {
    if (built_in_proposals[[name]]$c_num == cnum ||
        built_in_proposals[[name]]$c_num + 1 == cnum) {
      return(built_in_proposals[[name]]$transform_params(f_params))

    }
  }
  return(f_params)
}

#' @noRd
cache_proposal_c <- function(c_num, proposal) {
  n_params <- length(proposal$f_params)

  f_params <- 0

  if (n_params > 0) {
    f_params <- built_in_pars_trans(proposal$f_params, c_num)

    f_params <- unlist(proposal$f_params)
  }

  .Call(
    C_cache_grid,
    c_num,
    proposal$data$x,
    proposal$data$s_upper,
    proposal$data$p_a,
    proposal$data$s_upper_lower,
    proposal$areas,
    proposal$steps_number,
    proposal$sampling_probabilities,
    proposal$unif_scaler,
    proposal$lt_properties,
    proposal$rt_properties,
    proposal$alpha,
    proposal$symmetric,
    f_params,
    n_params,
    proposal$proposal_bounds[1],
    proposal$proposal_bounds[2]
  )

}



#' @noRd
cache_user_proposal_c <- function(proposal) {
  if (!is_valid_proposal(proposal)) {
    cli::cli_abort(c("x" = "{.strong This proposal is not valid}",
                     "i" = "Ensure that the proposal was created using {.fn build_proposal}"))
  }

  if (proposal$lock %in% stors_env$user_session_cached_proposals_locks[["lock"]]) {
    cli::cli_inform(
      "Detected cached proposal for this distribution.

                   {.strong Replacing with a new proposal.}"
    )

    c_num <- stors_env$user_session_cached_proposals_locks[stors_env$user_session_cached_proposals_locks$lock == proposal$lock, ]$cnum

    return(c_num)
  }

  if (stors_env$user_cnum_counter > 199) {
    cli::cli_abort(c("x" =   "Cache limit exceeded: Users can cache up to { 100} proposals in memory",
                     "i" =  "To clear cache, consider removing old proposals before adding new ones"))
  }

  c_num <- stors_env$user_cnum_counter

  cache_proposal_c(c_num, proposal)

  user_session_cached_proposals_locks <- data.frame(lock = proposal$lock, cnum = c_num)

  stors_env$user_session_cached_proposals_locks <- rbind(
    stors_env$user_session_cached_proposals_locks,
    user_session_cached_proposals_locks
  )

  stors_env$user_cnum_counter <- stors_env$user_cnum_counter + 1

  return(c_num)
}

#' @noRd
free_cache_cnum_c <- function(c_num) {
  .Call(C_free_cache_cnum, c_num)
}

#' @noRd
save_builtin_proposal <- function(c_num, proposal) {
  if (!dir.exists(stors_env$builtin_proposals_dir))
    dir.create(stors_env$builtin_proposals_dir, recursive = TRUE)

  proposals_file_path <- file.path(stors_env$builtin_proposals_dir, paste0(c_num, ".rds"))
  saveRDS(proposal, proposals_file_path)
}

#' @noRd
cached_proposal_info <- function(cnum) {
  .Call(C_grid_info, cnum)
}


#' Delete Built-in Proposal
#'
#' @description
#' This function deletes built-in proposals from disk by specifying the sampling function and proposal type.
#' It is useful for managing cached proposals and freeing up storage space.
#'
#' @param sampling_function String. The name of the sampling distribution's function in STORS.
#' For example, \code{"srgamma"} or \code{"srchisq"}.
#' @param proposal_type String. Either \code{"custom"} to delete the custom proposal or \code{"scaled"} to delete the scaled proposal.
#' Defaults to \code{"custom"}.
#'
#' @details
#' The function looks for the specified proposal type associated with the sampling function in the built-in proposals directory.
#' If the proposal exists, it deletes the corresponding proposal file from disk and frees its cached resources.
#' If the specified sampling function or proposal type does not exist, an error is thrown.
#'
#' @return
#' A message indicating the status of the deletion process, or an error if the operation fails.
#'
#' @examples
#'
#' # The following examples are not run, since if they are run the srgamma and
#' # srnorm samplers will no longer work until a new grid is built for them.
#' # This causes problems if the examples are run by CRAN checks or the website
#' # build system.
#' \dontrun{
#' # Delete a custom proposal for the srgamma function (uncomment to run)
#' delete_built_in_proposal(sampling_function = "srgamma", proposal_type = "custom")
#'
#' # Delete a scaled proposal for the srnorm function (uncomment to run)
#' delete_built_in_proposal(sampling_function = "srnorm", proposal_type = "scaled")
#' }
#'
#' @export
delete_built_in_proposal <- function(sampling_function, proposal_type = "custom") {
  proposal_type <- match.arg(proposal_type, c("scaled", "custom"))

  if (sampling_function %in% names(built_in_proposals)) {
    if (proposal_type == "scaled") {
      proposal_number <- built_in_proposals[[sampling_function]]$c_num
    } else {
      proposal_number <- built_in_proposals[[sampling_function]]$c_num + 1
    }

  } else {
    cli::cli_abort(c("x" = "The sampling function {.fn {sampling_function}} does not exist!",
                     "i" = "Please check the function name or ensure it is correctly defined"))
  }

  builtin_proposals <- list.files(stors_env$builtin_proposals_dir)

  for (proposal_name in builtin_proposals) {
    proposal_path <- file.path(stors_env$builtin_proposals_dir, proposal_name)
    proposal <- readRDS(proposal_path)

    if ("lock" %in% names(proposal)) {
      temp <- proposal[setdiff(names(proposal), "lock")]
      key <- digest(temp)

      if (key == proposal$lock) {
        if (proposal$cnum == proposal_number) {
          file.remove(proposal_path)
          free_cache_cnum_c(proposal$cnum)
          cli::cli_alert_success("Proposal number {.val {proposal$cnum}} deleted")
        }

      }

    }

  }

}

Try the stors package in your browser

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

stors documentation built on April 3, 2025, 6:16 p.m.