From da41396840ec9d26aa4818a5d84883a87041ff90 Mon Sep 17 00:00:00 2001 From: Dinakar <26552821+cicdguy@users.noreply.github.com> Date: Wed, 5 Oct 2022 04:28:33 -0500 Subject: [PATCH] Minor updates (#2) --- .github/workflows/check.yaml | 15 -- .gitignore | 1 + .lintr | 5 +- DESCRIPTION | 2 +- R/RcppExports.R | 7 +- R/functions.R | 271 ++++++++++++----------- R/zzzz.R | 2 +- README.Rmd | 17 +- README.md | 100 +++++---- _pkgdown.yml | 2 +- cran-comments.md | 32 +-- inst/WORDLIST | 35 +++ man/augmentGrid.Rd | 4 +- man/createGrid.Rd | 8 +- man/figures/README-unnamed-chunk-2-1.png | Bin 20762 -> 33073 bytes man/figures/README-unnamed-chunk-3-1.png | Bin 20762 -> 33073 bytes man/figures/README-unnamed-chunk-4-1.png | Bin 20416 -> 32818 bytes man/figures/README-unnamed-chunk-6-1.png | Bin 28137 -> 40862 bytes man/figures/README-unnamed-chunk-7-1.png | Bin 28137 -> 40862 bytes man/obtainDesign.Rd | 16 +- man/powerPlot.Rd | 6 +- tests/testthat/test-augmentGrid.R | 4 +- tests/testthat/test-createGrid.R | 4 +- tests/testthat/test-isAugmentedGrid.R | 2 +- tests/testthat/test-isBasicGrid.R | 16 +- tests/testthat/test-isManderGrid.R | 93 +++++++- tests/testthat/test-obtainDesign.R | 164 +++++++------- tests/testthat/test-validation.R | 38 ++-- 28 files changed, 484 insertions(+), 360 deletions(-) create mode 100644 inst/WORDLIST diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index f73e98b..2bfb840 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -1,4 +1,3 @@ - --- name: Check 🛠 @@ -48,23 +47,9 @@ jobs: if: github.event_name == 'pull_request' name: Spell Check 🆎 uses: insightsengineering/r.pkg.template/.github/workflows/spelling.yaml@main - links: - if: github.event_name == 'pull_request' - name: Check URLs 🌐 - uses: insightsengineering/r.pkg.template/.github/workflows/links.yaml@main - version: - name: Version Check 🏁 - uses: insightsengineering/r.pkg.template/.github/workflows/version.yaml@main - licenses: - name: License Check 🃏 - uses: insightsengineering/r.pkg.template/.github/workflows/licenses.yaml@main style: if: github.event_name == 'pull_request' name: Style Check 👗 uses: insightsengineering/r.pkg.template/.github/workflows/style.yaml@main with: auto-update: true - grammar: - if: github.event_name == 'pull_request' - name: Grammar Check 🔤 - uses: insightsengineering/r.pkg.template/.github/workflows/grammar.yaml@main diff --git a/.gitignore b/.gitignore index 9f96335..94c6c3e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ vignettes/*.R vignettes/*.html vignettes/*.md .vscode/ +lib/ diff --git a/.lintr b/.lintr index c249a44..979ff26 100644 --- a/.lintr +++ b/.lintr @@ -3,5 +3,8 @@ linters: linters_with_defaults( line_length_linter = line_length_linter(120), cyclocomp_linter = NULL, - object_usage_linter = NULL + object_usage_linter = NULL, + object_name_linter = NULL, + commented_code_linter = NULL, + vector_logic_linter = NULL ) diff --git a/DESCRIPTION b/DESCRIPTION index cd4772f..8d6623f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -22,7 +22,7 @@ Encoding: UTF-8 LazyData: true URL: https://github.com/openpharma/mtdesign BugReports: https://github.com/openpharma/mtdesign/issues -RoxygenNote: 7.1.2 +RoxygenNote: 7.2.1 Imports: dplyr, ggplot2, diff --git a/R/RcppExports.R b/R/RcppExports.R index 38f5c05..477f47a 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -2,14 +2,13 @@ # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 simonProb <- function(p, n1, r1, n, r) { - .Call('_mtdesign_simonProb', PACKAGE = 'mtdesign', p, n1, r1, n, r) + .Call("_mtdesign_simonProb", PACKAGE = "mtdesign", p, n1, r1, n, r) } manderProb <- function(p, n1, r1, r2, n, r) { - .Call('_mtdesign_manderProb', PACKAGE = 'mtdesign', p, n1, r1, r2, n, r) + .Call("_mtdesign_manderProb", PACKAGE = "mtdesign", p, n1, r1, r2, n, r) } augmentGridC <- function(d) { - .Call('_mtdesign_augmentGridC', PACKAGE = 'mtdesign', d) + .Call("_mtdesign_augmentGridC", PACKAGE = "mtdesign", d) } - diff --git a/R/functions.R b/R/functions.R index 7f2567b..afdd9ed 100644 --- a/R/functions.R +++ b/R/functions.R @@ -3,18 +3,20 @@ .onLoad <- function(libname, pkgname) { logger::log_layout( - logger::layout_glue_generator(format = '{namespace} {time} {level} {fn}: {msg}'), - namespace="mtdesign" + logger::layout_glue_generator(format = "{namespace} {time} {level} {fn}: {msg}"), + namespace = "mtdesign" ) } -.onUnload <- function (libpath) { +.onUnload <- function(libpath) { library.dynam.unload("mtdesign", libpath) } isBasicGrid <- function(grid) { logger::log_debug("Entry") - if (!methods::is(grid, "data.frame")) return (FALSE) + if (!methods::is(grid, "data.frame")) { + return(FALSE) + } columnsRequired <- c("p0", "p1", "nStage1", "nTotal", "rFutility", "rTotal") rv <- length(intersect(names(grid), columnsRequired)) == length(columnsRequired) logger::log_debug("Exit") @@ -31,15 +33,19 @@ isManderGrid <- function(grid) { isAugmented <- function(grid) { logger::log_debug("Entry") - if (!isBasicGrid(grid)) return (NA) - columnsRequired <- c("Type1", "Type2", "PETNull", "PETAlt", "AveSizeNull", - "AveSizeAlt") + if (!isBasicGrid(grid)) { + return(NA) + } + columnsRequired <- c( + "Type1", "Type2", "PETNull", "PETAlt", "AveSizeNull", + "AveSizeAlt" + ) logger::log_debug("Exit") rv <- length(intersect(names(grid), columnsRequired)) == length(columnsRequired) return(rv) } -#'Create a grid of candidate designs +#' Create a grid of candidate designs #' #' @param p0 the response rate under the null hypothesis #' @param p1 the response rate under the alternate hypothesis @@ -53,56 +59,56 @@ isAugmented <- function(grid) { #' @param mander is a Mander & Thompson or a Simon's design required? #' @return a tibble. See Usage notes for a list and description of columns. #' @examples -#' #Standard use for a Simon's 2-stage design -#' x <- createGrid(p0=0.1, p1=0.5, alpha=0.1, beta=0.1, mander=FALSE) -#' #Custom search bounds for a Mander & Thompson design -#' y <- createGrid(p0=0.1, p1=0.4, alpha=0.1, beta=0.1, nMin=20, nMax=30) +#' # Standard use for a Simon's 2-stage design +#' x <- createGrid(p0 = 0.1, p1 = 0.5, alpha = 0.1, beta = 0.1, mander = FALSE) +#' # Custom search bounds for a Mander & Thompson design +#' y <- createGrid(p0 = 0.1, p1 = 0.4, alpha = 0.1, beta = 0.1, nMin = 20, nMax = 30) #' @importFrom magrittr %>% #' @importFrom tibble tibble #' @export createGrid <- function(p0, p1, - alpha=0.1, - beta=NA, - power=ifelse(is.na(beta), 0.9, 1 - beta), - nMin=NA, - nMax=NA, - mander=TRUE - ) -{ + alpha = 0.1, + beta = NA, + power = ifelse(is.na(beta), 0.9, 1 - beta), + nMin = NA, + nMax = NA, + mander = TRUE) { logger::log_debug("Entry") - #Validation + # Validation if (is.na(p0)) stop("You must provide a value for p0") if (is.na(p1)) stop("You must provide a value for p1") if (p0 < 0 || p0 > 1) stop("p0 must be between 0 and 1") if (p1 < 0 || p1 > 1) stop("p1 must be between 0 and 1") if (p1 <= p0) stop("p1 must be strictly greater than p0") if (alpha < 0 || alpha > 1) stop("alpha must be between 0 and 1") - if (!is.na(power)) if (power < 0 || power > 1) { - stop("power must be between 0 and 1") + if (!is.na(power)) { + if (power < 0 || power > 1) { + stop("power must be between 0 and 1") + } } - if (!is.na(beta)) if (beta < 0 || beta > 1) { - stop("beta must be between 0 and 1") + if (!is.na(beta)) { + if (beta < 0 || beta > 1) { + stop("beta must be between 0 and 1") + } } - if (!is.na(power) & !is.na(beta) & !isTRUE(all.equal(beta, (1-power)))) { + if (!is.na(power) & !is.na(beta) & !isTRUE(all.equal(beta, (1 - power)))) { stop("Inconsistent values for beta and power") } if (is.na(power) & is.na(beta)) { stop("Both beta and power are null. At least one must be not null.") } - #Initialise + # Initialise if (is.na(beta)) beta <- 1 - power if (is.na(nMin) | is.na(nMax)) { - bounds <- searchBounds(p0, p1, alpha, beta, twoSided=FALSE) + bounds <- searchBounds(p0, p1, alpha, beta, twoSided = FALSE) } - if (is.na(nMin)) - { + if (is.na(nMin)) { nMin <- bounds["min"] logger::log_debug(paste0("Using default value for nMin: ", nMin)) } - if (is.na(nMax)) - { + if (is.na(nMax)) { nMax <- bounds["max"] logger::log_debug(paste0("Using default value for nMax: ", nMax)) } @@ -119,11 +125,11 @@ createGrid <- function(p0, ) if (nMax <= nMin) stop("nMax must be strictly greater than nMin.") - #Begin + # Begin nTotal <- nMin:nMax - nStage1 <- 1:(nMax-1) - rTotal <- 0:(nMax-1) - rFutility <- 0:(nMax-1) + nStage1 <- 1:(nMax - 1) + rTotal <- 0:(nMax - 1) + rFutility <- 0:(nMax - 1) if (mander) { rSuccess <- 0:nMax } else { @@ -135,11 +141,11 @@ createGrid <- function(p0, # dplyr::expand(nTotal, nStage1, rTotal, rFutility, rSuccess) # so build up and filter in stages. d <- tibble::tibble() %>% tidyr::expand(nTotal, nStage1) - d <- d %>% dplyr::filter(nTotal >= nMin, nStage1 < nTotal) + d <- d %>% dplyr::filter(nTotal >= nMin, nStage1 < nTotal) logger::log_trace(paste0("Building grid - nTotal, nStage1: ", nrow(d))) d <- d %>% tidyr::expand(tidyr::nesting(nTotal, nStage1), rTotal) - d <- d %>% dplyr::filter(rTotal < nTotal) + d <- d %>% dplyr::filter(rTotal < nTotal) logger::log_trace(paste0("Building grid - nTotal, nStage1, rTotal: ", nrow(d))) d <- d %>% tidyr::expand(tidyr::nesting(nTotal, nStage1, rTotal), rFutility) @@ -175,17 +181,17 @@ createGrid <- function(p0, } d <- d %>% dplyr::mutate( - p0=p0, - p1=p1, - Alpha=alpha, - Beta=beta + p0 = p0, + p1 = p1, + Alpha = alpha, + Beta = beta ) if (!mander) { d <- d %>% dplyr::select(-rSuccess) } logger::log_trace(paste0("Grid has ", nrow(d), " rows.")) logger::log_debug("Exit") - return (d) + return(d) } #' Obtain default bounds for the construction of the search grid. @@ -201,20 +207,20 @@ createGrid <- function(p0, #' Fleiss et al; "min" - the lower bound, 0.8*n; "max" - the upper bound, 2*n. #' \code{floor()} and \code{ceiling()} are applied as appropriate. #' @export -searchBounds <- function(p0, p1, alpha=0.05, beta=0.2, twoSided=TRUE) { +searchBounds <- function(p0, p1, alpha = 0.05, beta = 0.2, twoSided = TRUE) { logger::log_debug("Entry") if (twoSided) alpha <- alpha / 2 # Sample size formula based on Fleiss JL, Levin B and Paik MC (2003). # Statistical Methods for Rates and Proportions, Third Edition, # John Wiley & Sons, New York - n <- ((stats::qnorm(1 - alpha)*sqrt(p0*(1-p0)) + - stats::qnorm(1 - beta)*sqrt(p1*(1-p1))) / - (p0 - p1)) ^ 2 + n <- ((stats::qnorm(1 - alpha) * sqrt(p0 * (1 - p0)) + + stats::qnorm(1 - beta) * sqrt(p1 * (1 - p1))) / + (p0 - p1))^2 # Continuity correction from Fleiss et al - n <- n + 1/abs(p0-p1) - #Bounds based on ???? - rv <- c("n"=ceiling(n), "min"=floor(n*0.8), "max"=ceiling(n*2)) + n <- n + 1 / abs(p0 - p1) + # Bounds based on ???? + rv <- c("n" = ceiling(n), "min" = floor(n * 0.8), "max" = ceiling(n * 2)) logger::log_debug("Exit") return(rv) } @@ -234,10 +240,10 @@ searchBounds <- function(p0, p1, alpha=0.05, beta=0.2, twoSided=TRUE) { #' paralellisation is requested and needed, an exception is thrown if the #' parallel package is not available. #' @examples -#' x <- createGrid(p0=0.1, p1=0.30, alpha=0.1, beta=0.1, nMin=24, nMax=32) %>% -#' augmentGrid(parallel=FALSE) +#' x <- createGrid(p0 = 0.1, p1 = 0.30, alpha = 0.1, beta = 0.1, nMin = 24, nMax = 32) %>% +#' augmentGrid(parallel = FALSE) #' @export -augmentGrid <- function(d, parallel=TRUE, cores=NA, minChunkSize=100000) { +augmentGrid <- function(d, parallel = TRUE, cores = NA, minChunkSize = 100000) { logger::log_debug("Entry") k <- d %>% nrow() if (parallel) { @@ -248,7 +254,7 @@ augmentGrid <- function(d, parallel=TRUE, cores=NA, minChunkSize=100000) { "] is less than the minimum chunk size [", minChunkSize, "]. Parallelisation will not occur" ), - namespace="mtdesign" + namespace = "mtdesign" ) parallel <- FALSE } @@ -263,24 +269,26 @@ augmentGrid <- function(d, parallel=TRUE, cores=NA, minChunkSize=100000) { logger::log_trace("Starting parallelisation") logger::log_trace(paste0("Requesting ", cores, " cores")) logger::log_trace(paste0("k is ", k)) - chunkSize <- ceiling(k/cores) + chunkSize <- ceiling(k / cores) logger::log_trace(paste0("Creating chunk list. Chunk size is ", chunkSize)) - tmp <- d %>% dplyr::mutate(Chunk=ceiling(dplyr::row_number()/chunkSize)) + tmp <- d %>% dplyr::mutate(Chunk = ceiling(dplyr::row_number() / chunkSize)) parallelList <- tmp %>% dplyr::group_by(Chunk) %>% dplyr::group_map(function(.x, .y) .x) logger::log_trace("Creating cluster") cluster <- parallel::makeCluster(cores) logger::log_trace("Initialising nodes") - parallel::clusterEvalQ(cluster, { library(parallel) }) + parallel::clusterEvalQ(cluster, { + library(parallel) + }) logger::log_trace("Running parLapply") d <- parallel::parLapply( cluster, parallelList, augmentGrid, - parallel=FALSE + parallel = FALSE ) %>% - dplyr::bind_rows() + dplyr::bind_rows() logger::log_trace("Stopping cluster") suppressWarnings(parallel::stopCluster(cluster)) } else { @@ -293,18 +301,18 @@ augmentGrid <- function(d, parallel=TRUE, cores=NA, minChunkSize=100000) { ) ) } - #The return type of a data frame from Rcpp is unreliable, so wrap it here - #to make sure all is good + # The return type of a data frame from Rcpp is unreliable, so wrap it here + # to make sure all is good cls <- class(d) d <- tibble::as_tibble(augmentGridC(d)) class(d) <- cls } # Bug fix if (isManderGrid(d)) { - d <- d %>% dplyr::mutate(rSuccess=as.integer(rSuccess)) + d <- d %>% dplyr::mutate(rSuccess = as.integer(rSuccess)) } logger::log_debug("Exit") - return (d) + return(d) } @@ -349,38 +357,38 @@ augmentGrid <- function(d, parallel=TRUE, cores=NA, minChunkSize=100000) { #' under constraints - for example with fixed stage sizes. #' @examples #' \donttest{ -#' #Standard use (Simon's 2-stage design) -#' createGrid(p0=0.05, p1=0.25, alpha=0.05, beta=0.2, mander=FALSE) %>% -#' augmentGrid(parallel=FALSE) %>% +#' # Standard use (Simon's 2-stage design) +#' createGrid(p0 = 0.05, p1 = 0.25, alpha = 0.05, beta = 0.2, mander = FALSE) %>% +#' augmentGrid(parallel = FALSE) %>% +#' obtainDesign() +#' # Constrained stage sizes +#' createGrid(p0 = 0.25, p1 = 0.45, alpha = 0.05, beta = 0.2) %>% +#' dplyr::filter(nStage1 == 8) %>% +#' augmentGrid(parallel = FALSE) %>% #' obtainDesign() -#' #Constrained stage sizes -#'createGrid(p0=0.25, p1=0.45, alpha=0.05, beta=0.2) %>% -#' dplyr::filter(nStage1 == 8) %>% -#' augmentGrid(parallel=FALSE) %>% -#' obtainDesign() #' } #' @export -obtainDesign <- function(grid=NULL, - p0=NA, - p1=NA, - alpha=ifelse(is.null(grid), 0.05, NA), - beta=ifelse(is.null(grid), 0.1, NA), - fullGrid=FALSE, +obtainDesign <- function(grid = NULL, + p0 = NA, + p1 = NA, + alpha = ifelse(is.null(grid), 0.05, NA), + beta = ifelse(is.null(grid), 0.1, NA), + fullGrid = FALSE, ...) { logger::log_debug("Entry") - #Initialise + # Initialise dots <- list(...) - #Validate + # Validate if (is.null(grid)) { - if (is.na(p0) | is.na(p1) | is.na(alpha) | is.na(beta)) { + if (is.na(p0) | is.na(p1) | is.na(alpha) | is.na(beta)) { stop("If you do not supply a grid, then you must supply all of p0, p1, alpha and beta.") } - if (p0 <= 0 | p0 >= 1) stop ("p0 must be between 0 and 1") - if (p1 <= 0 | p1 >= 1) stop ("p1 must be between 0 and 1") - if (alpha <= 0 | alpha >= 1) stop ("alpha must be between 0 and 1") - if (beta <= 0 | beta >= 1) stop ("beta must be between 0 and 1") + if (p0 <= 0 | p0 >= 1) stop("p0 must be between 0 and 1") + if (p1 <= 0 | p1 >= 1) stop("p1 must be between 0 and 1") + if (alpha <= 0 | alpha >= 1) stop("alpha must be between 0 and 1") + if (beta <= 0 | beta >= 1) stop("beta must be between 0 and 1") } else { - if(!(grid %>% isBasicGrid())) { + if (!(grid %>% isBasicGrid())) { stop("Grid must be a tibble created by createGrid()") } if (!is.na(p0)) { @@ -397,26 +405,28 @@ obtainDesign <- function(grid=NULL, } } - #Begin + # Begin # Calls to do.call are required to separate the dot list appropriately # Function name as string as workaround for https://github.com/daroczig/logger/issues/114 - if (is.null(grid)) grid <- - do.call( - "createGrid", - c( - list(p0=p0, p1=p1, alpha=alpha, beta=beta), - dots[names(dots) %in% names(formals(mtdesign::createGrid))] + if (is.null(grid)) { + grid <- + do.call( + "createGrid", + c( + list(p0 = p0, p1 = p1, alpha = alpha, beta = beta), + dots[names(dots) %in% names(formals(mtdesign::createGrid))] + ) ) - ) + } if (!(grid %>% isAugmented())) { grid <- - do.call( - "augmentGrid", - c( - list(d=grid), - dots[names(dots) %in% names(formals(mtdesign::augmentGrid))] + do.call( + "augmentGrid", + c( + list(d = grid), + dots[names(dots) %in% names(formals(mtdesign::augmentGrid))] + ) ) - ) } if (fullGrid) { return(grid) @@ -435,7 +445,7 @@ obtainDesign <- function(grid=NULL, rv[[1]] <- acceptableGrid %>% dplyr::slice(which.min(AveSizeNull)) %>% dplyr::mutate( - Criterion=ifelse( + Criterion = ifelse( acceptableGrid %>% isManderGrid(), "optimalNull", "optimal" @@ -445,7 +455,7 @@ obtainDesign <- function(grid=NULL, dplyr::slice_min(nTotal) %>% dplyr::slice_min(AveSizeNull) %>% dplyr::mutate( - Criterion=ifelse( + Criterion = ifelse( acceptableGrid %>% isManderGrid(), "minimaxNull", "minimax" @@ -454,16 +464,16 @@ obtainDesign <- function(grid=NULL, if (isManderGrid(grid)) { rv[[3]] <- acceptableGrid %>% dplyr::slice_min(AveSizeAlt) %>% - dplyr::mutate(Criterion="optimalAlt") + dplyr::mutate(Criterion = "optimalAlt") rv[[4]] <- acceptableGrid %>% dplyr::slice_min(nTotal) %>% dplyr::slice_min(AveSizeAlt) %>% - dplyr::mutate(Criterion="minimaxAlt") + dplyr::mutate(Criterion = "minimaxAlt") } rv <- dplyr::bind_rows(rv) class(rv) <- class(grid) logger::log_debug("Exit") - return (rv) + return(rv) } } @@ -475,22 +485,22 @@ obtainDesign <- function(grid=NULL, #' be plotted #' @return the ggplot object containing the power curve(s) #' @examples -#' createGrid(p0=0.05, p1=0.25, alpha=0.05, beta=0.2, mander=FALSE) %>% -#' augmentGrid(cores=2) %>% +#' createGrid(p0 = 0.05, p1 = 0.25, alpha = 0.05, beta = 0.2, mander = FALSE) %>% +#' augmentGrid(cores = 2) %>% #' obtainDesign() %>% -#' powerPlot(probs=seq(0, 0.5, 0.025)) +#' powerPlot(probs = seq(0, 0.5, 0.025)) #' @export -powerPlot <- function(grid, probs=seq(0, 1, 0.01)) { +powerPlot <- function(grid, probs = seq(0, 1, 0.01)) { logger::log_debug("Entry") if (is.null(grid)) stop("grid cannot be null") if (!isBasicGrid(grid)) stop("Grid must be a tibble created by createGrid()") if (isManderGrid(grid)) { plotData <- grid %>% - dplyr::mutate(Design=c(1:nrow(grid))) %>% + dplyr::mutate(Design = c(seq_len(nrow(grid)))) %>% dplyr::group_by(Design) %>% dplyr::mutate( - Label=paste0( + Label = paste0( "(", rFutility, " ", @@ -530,19 +540,19 @@ powerPlot <- function(grid, probs=seq(0, 1, 0.01)) { p0, p1 ), - pResponse=probs + pResponse = probs ) %>% dplyr::group_by(Design, pResponse) %>% dplyr::mutate( - pReject=1 - manderProb(pResponse, nStage1, rFutility, rSuccess, nTotal, rTotal) + pReject = 1 - manderProb(pResponse, nStage1, rFutility, rSuccess, nTotal, rTotal) ) %>% dplyr::ungroup() } else { plotData <- grid %>% - dplyr::mutate(Design=1:nrow(grid)) %>% + dplyr::mutate(Design = seq_len(nrow(grid))) %>% dplyr::group_by(Design) %>% dplyr::mutate( - Label=paste0( + Label = paste0( rFutility, "/", nStage1, @@ -550,7 +560,7 @@ powerPlot <- function(grid, probs=seq(0, 1, 0.01)) { rTotal, "/", nTotal - ) + ) ) %>% dplyr::select( Design, @@ -577,28 +587,29 @@ powerPlot <- function(grid, probs=seq(0, 1, 0.01)) { p0, p1 ), - pResponse=probs + pResponse = probs ) %>% dplyr::group_by(Design, pResponse) %>% dplyr::mutate( - pReject=1 - simonProb(pResponse, nStage1, rFutility, nTotal, rTotal) + pReject = 1 - simonProb(pResponse, nStage1, rFutility, nTotal, rTotal) ) %>% dplyr::ungroup() } plot <- plotData %>% - ggplot2::ggplot() + - ggplot2::geom_line( - ggplot2::aes( - x=pResponse, - y=pReject, - colour=Label) - ) + - ggplot2::labs( - x="True response rate", - y="p(Signal detected)" - ) + - ggplot2::theme_light() + - ggplot2::theme(legend.title=ggplot2::element_blank()) + ggplot2::ggplot() + + ggplot2::geom_line( + ggplot2::aes( + x = pResponse, + y = pReject, + colour = Label + ) + ) + + ggplot2::labs( + x = "True response rate", + y = "p(Signal detected)" + ) + + ggplot2::theme_light() + + ggplot2::theme(legend.title = ggplot2::element_blank()) logger::log_debug("Exit") return(plot) } diff --git a/R/zzzz.R b/R/zzzz.R index 08cf6b2..bc1f34f 100644 --- a/R/zzzz.R +++ b/R/zzzz.R @@ -12,7 +12,7 @@ p0 <- NULL p1 <- NULL pReject <- NULL pResponse <- NULL -rFutility<- NULL +rFutility <- NULL rSuccess <- NULL rTotal <- NULL Chunk <- NULL diff --git a/README.Rmd b/README.Rmd index 3d8b209..02328c0 100644 --- a/README.Rmd +++ b/README.Rmd @@ -20,6 +20,7 @@ knitr::opts_chunk$set( [![CRAN status](https://www.r-pkg.org/badges/version/mtdesign)](https://CRAN.R-project.org/package=mtdesign) +[![Test Coverage](https://raw.githubusercontent.com/openpharma/mtdesign/_xml_coverage_reports/data/main/badge.svg)](https://github.com/openpharma/mtdesign/blob/_xml_coverage_reports/data/main/coverage.xml) ## Introduction @@ -73,12 +74,12 @@ Obtaining the equivalent Mander & Thompson designs requires only a small change ```{r} manderDesign <- obtainDesign( - p0 = 0.05, - p1 = 0.25, - alpha = 0.05, - beta = 0.2, - cores = maxCores - ) + p0 = 0.05, + p1 = 0.25, + alpha = 0.05, + beta = 0.2, + cores = maxCores +) manderDesign %>% select(-Alpha, -Beta, -p0, -p1) %>% @@ -169,9 +170,11 @@ The `parallel` package is required for parallelisation. If parallelisation is b ## Troubleshooting If, when installing or using the `mtdesign` package, you get an error regarding a syntax error in an`.hpp` file, similar to the following -``` + +```R .../BH/include/boost/math/tools/fraction.hpp:84:48: error: ‘long double’ is not a class, struct, or union type using value_type = typename T::value_type; ``` + the issue is most likely a mismatch between the g++ compiler being used and the headers supplied by the `BH` package. There are only two solutions that I know of: * Upgrade g++ diff --git a/README.md b/README.md index c0e6b1a..c1c6c98 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # mtdesign @@ -7,6 +6,9 @@ [![CRAN status](https://www.r-pkg.org/badges/version/mtdesign)](https://CRAN.R-project.org/package=mtdesign) +[![Test +Coverage](https://raw.githubusercontent.com/openpharma/mtdesign/_xml_coverage_reports/data/main/badge.svg)](https://github.com/openpharma/mtdesign/blob/_xml_coverage_reports/data/main/coverage.xml) + ## Introduction @@ -32,7 +34,7 @@ You can install the development version of `mtdesign` from ## Set up vignette environment -``` r +```r # By policy, on CRAN, use only two cores, no matter how many are available. if (requireNamespace("parallel", quietly = TRUE)) { maxCores <- parallel::detectCores() @@ -49,7 +51,7 @@ interest but those with a response rate of at least 25% are worthy of further development. A Simon’s 2-stage design to seek an efficacy signal with a significance level of 5% and a power of 80% is required. -``` r +```r library(mtdesign) library(knitr) library(dplyr) @@ -70,7 +72,7 @@ simonDesign %>% ``` | nTotal | nStage1 | rTotal | rFutility | Type1 | Type2 | PETNull | AveSizeNull | Criterion | -|-------:|--------:|-------:|----------:|------:|------:|--------:|------------:|:----------| +| -----: | ------: | -----: | --------: | ----: | ----: | ------: | ----------: | :-------- | | 17 | 9 | 2 | 0 | 0.047 | 0.188 | 0.63 | 12.0 | optimal | | 16 | 12 | 2 | 0 | 0.043 | 0.199 | 0.54 | 13.8 | minimax | @@ -81,7 +83,7 @@ the power level achieved is 100% - 18.8% = 81.2%. The power curves for both designs are easily plotted. -``` r +```r powerPlot(simonDesign) ``` @@ -90,14 +92,14 @@ powerPlot(simonDesign) Obtaining the equivalent Mander & Thompson designs requires only a small change to the calls. -``` r +```r manderDesign <- obtainDesign( - p0 = 0.05, - p1 = 0.25, - alpha = 0.05, - beta = 0.2, - cores = maxCores - ) + p0 = 0.05, + p1 = 0.25, + alpha = 0.05, + beta = 0.2, + cores = maxCores +) manderDesign %>% select(-Alpha, -Beta, -p0, -p1) %>% @@ -105,13 +107,14 @@ manderDesign %>% ``` | nTotal | nStage1 | rTotal | rFutility | rSuccess | Type1 | Type2 | PETNull | PETAlt | AveSizeNull | AveSizeAlt | Criterion | -|-------:|--------:|-------:|----------:|---------:|------:|------:|--------:|-------:|------------:|-----------:|:------------| +| -----: | ------: | -----: | --------: | -------: | ----: | ----: | ------: | -----: | ----------: | ---------: | :---------- | | 17 | 9 | 2 | 0 | 2 | 0.047 | 0.19 | 0.64 | 0.47 | 11.9 | NA | optimalNull | | 16 | 12 | 2 | 0 | 2 | 0.043 | 0.20 | 0.56 | 0.64 | 13.8 | NA | minimaxNull | | 17 | 9 | 2 | 0 | 2 | 0.047 | 0.19 | 0.64 | 0.47 | 11.9 | NA | optimalAlt | | 16 | 12 | 2 | 0 | 2 | 0.043 | 0.20 | 0.56 | 0.64 | 13.8 | NA | minimaxAlt | -``` r +```r + powerPlot(manderDesign) ``` @@ -125,7 +128,7 @@ stage design is 0/9 2/17. That’s close to n1 = 8, n = 16. Is there a (slightly) sub-optimal design that has n1 = 8, n = 16? -``` r +```r x <- createGrid(p0 = 0.05, p1 = 0.25, alpha = 0.05, beta = 0.2, mander = FALSE) y <- x %>% filter(nStage1 == 8, nTotal == 16) @@ -144,7 +147,7 @@ if (nrow(z) == 0) { No, there isn’t. How close can we get? -``` r +```r z1 <- y %>% augmentGrid() bestSize <- z1 %>% @@ -159,12 +162,13 @@ bestSize %>% ``` | nTotal | nStage1 | rTotal | rFutility | Type1 | Type2 | PETNull | AveSizeNull | -|-------:|--------:|-------:|----------:|------:|------:|--------:|------------:| +| -----: | ------: | -----: | --------: | ----: | ----: | ------: | ----------: | | 16 | 8 | 2 | 0 | 0.039 | 0.229 | 0.66 | 10.7 | Best sub-optimal design with required significance level -``` r +```r + bestPower <- z1 %>% filter(Type2 < Beta) %>% slice_min(Type1) @@ -178,7 +182,7 @@ bestPower %>% ``` | nTotal | nStage1 | rTotal | rFutility | Type1 | Type2 | PETNull | AveSizeNull | -|-------:|--------:|-------:|----------:|------:|------:|--------:|------------:| +| -----: | ------: | -----: | --------: | ----: | ----: | ------: | ----------: | | 16 | 8 | 1 | 0 | 0.151 | 0.127 | 0.66 | 10.7 | Best sub-optimal design with required power @@ -194,7 +198,7 @@ level. The power curve for each of these designs can be compared with that for the globally optimal design. -``` r +```r plotData1 <- simonDesign %>% filter(Criterion == "optimal") %>% bind_rows(list(bestSize, bestPower)) @@ -207,15 +211,15 @@ powerPlot(plotData1) The `mtdesign` package consists of three main functions: -- `createGrid` creates the grid (of nStage1, rFutility, nTotal and - rTotal for Simon’s design or nStage1, rFutility, rSuccess, nTotal - and rTotal for a Mander & Thompson design) over which the brute - force search for the required design(s) is conducted -- `augmentGrid`takes a grid created by `createGrid` and adds columns - for probability of early termination, Type 1 error, Type 2 error and - expected sample size to it. -- `obtainDesign` takes an augmented grid and identifies the optimal - and minimax designs +* `createGrid` creates the grid (of nStage1, rFutility, nTotal and + rTotal for Simon’s design or nStage1, rFutility, rSuccess, nTotal and + rTotal for a Mander & Thompson design) over which the brute force + search for the required design(s) is conducted +* `augmentGrid`takes a grid created by `createGrid` and adds columns for + probability of early termination, Type 1 error, Type 2 error and + expected sample size to it. +* `obtainDesign` takes an augmented grid and identifies the optimal and + minimax designs ## Error and warning messages and logging @@ -240,13 +244,13 @@ attempt to speed up the evaluation of candidate designs. The `augmentGrid` function allows users some control over the parallelisation process: -- The `parallel` parameter defaults to `TRUE` and defines whether or - not paralellisation is to be used. -- The `cores` parameter specifies how many cores are to be used. The - default value, `NA` tells `mtdesign` to use all available (as - defined by `parallel::detectCores()`), cores. -- The `minChunkSize` determines the smallest grid of candidate designs - that will trigger paralellisation. The default value is `100000`. +* The `parallel` parameter defaults to `TRUE` and defines whether or not + paralellisation is to be used. +* The `cores` parameter specifies how many cores are to be used. The + default value, `NA` tells `mtdesign` to use all available (as defined + by `parallel::detectCores()`), cores. +* The `minChunkSize` determines the smallest grid of candidate designs + that will trigger paralellisation. The default value is `100000`. The `parallel` package is required for parallelisation. If parallelisation is both needed (ie the grid size exceeds `minChunkSize`) @@ -260,16 +264,18 @@ more rows, a warning is produced. If, when installing or using the `mtdesign` package, you get an error regarding a syntax error in an`.hpp` file, similar to the following - .../BH/include/boost/math/tools/fraction.hpp:84:48: error: ‘long double’ is not a class, struct, or union type using value_type = typename T::value_type; +```r +.../BH/include/boost/math/tools/fraction.hpp:84:48: error: ‘long double’ is not a class, struct, or union type using value_type = typename T::value_type; +``` the issue is most likely a mismatch between the g++ compiler being used and the headers supplied by the `BH` package. There are only two solutions that I know of: -- Upgrade g++ -- Downgrade the version of the `BH` package you are using. The - appropriate package version depends on the version of the g++ - compiler you are using. +* Upgrade g++ +* Downgrade the version of the `BH` package you are using. The + appropriate package version depends on the version of the g++ compiler + you are using. ## References @@ -277,8 +283,8 @@ solutions that I know of:
DW-2mVmcO$Q-bnD_Hb>yQcD~LeC*YXSu90>INgU)V66Byw zuK99#=Rjujqk8)gDsj6M!NthP*naZIt_vO2h`Ig`NEujg*cRuuW3dnoI6?<$G6yFo z&P}yA$fg$jZ?D~;VxEAu_?gPj#>i+jU2O>!J^Ts4J$1VC@8Ak7MwA{y6BCv0?&JhE z1nT--vJHaew~r42>)Ua2TrsooAit>SHOQ?HZW|SwJmJ%)>NgvjCF4HB)qAansy;qG zQc_YNx*>eQAiflH{`B-TV|@SN)gV{LQEulAZGgSUybeGm&f@9+s^|d>3ef^ehtv;x#wa&>Gv3#4v(EbfT{us$3+cP+|I@ZtTQL) zP)L7R0wJl2Rvtrdv%UZo@c+L(ZH#smTk=|Ne~B0wmAdssQ-R1vA>vUgm!}63GXVXT zyl=o{WdW}TVgX3gML?&|&dz{qxjOB*b?C4Mx#LK_61b9rm>8gwY5?ErO+Mp&`uzE% z^B!((M+sh^3m|C9bT9+p+cnVk;|T*lGBU2v!RvFVe>;eI0Q$s(T)x7n#iR569nPdJ zkYVo+PCm$@YIunyD$#4pnEL~#7p%7 goVeIT586 z^>TJ_C|h#@>E-q5!ot|t(b;m0uC8w1_wVh8zeLucSxz1H{|$0sr?VJHU OF$PU#Xo_SSNJAWECsJ1&2$po$a+Q`)U$wxkvD;r^EZq8-o3j#>T z<=oz1Xx42vCReRVeMH4e4Gj%-by#1|R9hDOud~qR7N{guG;gv>$pfpIF`$V3<`=lp zy#9J Fr$5=APtR9MMZ_-d30$> zNzF@k;M@;n%dS?FOfp5iT~BxCK&l-;ix%+PZ<^B=uzn4*t`RgHm|O;MMAm+Ly>N>N ztD>{ey_imGKwRjfEQaVX ~h!C~0=d(l}rL-_bVK@~iy^45a^0Mb`h zR%B#l!CkhKI<71)EpdTiAPKLXb8eti%*n}dnEvH^xzj+|cfJZc7f_yTh*D5f<6>jO z4DR{M#>xt&hX5bn*EkRq@TToSRn1SCh?Y3wF)wd*jjqYp; 2ZBY@km>5-B^qtk!B)6Z*JPyxsr9Luqr-D>gGbzK;1@$SQ(SwqVj=<6|Zho@? zlFR3jq&b?!e=@VUO{LxkKI`*R#8mk^u7F 5M)zSMekFbFc}57=ls z@C0gsx|@0vQT}xVfd_fhHK=ivCg`N9sQ9PR#T=whz+?O!N&^MT4q!UKR?@}aT>wXi zt*QtY@-e;J9*ozGMNN;SIE(=OfLN9d^FDT60VtWcqobpFd3n0!hTb4c0eDgwkneEx zMJ5tPSP5J5je;`YNd~Co7q;4WA P#zJE3)n!)YB@X6&Yocs$Xo%>(>Gj1iXd#xbRAc9pI}&>NU*ztk ziEAUNk&)Kz+}q2ATX63&z@8bq4e?|3TmkR}$nwjR9e{zM#{SpGz(0!$v3#h_dT&h% zgfS3^(SatsxVX4bE$arH5*{87 A;* zT9`jbqrvK0u4p^a53N|j@O<`N>msW7I45Ndt6zAaB$+b>bEz~Sd=-tVBO9Lf?KON6kK7cUN|HEHy3Oj5O zFsWYs73^SPXFu|7v>pEn`UAi@2qc<>y{~!$F)< 4upox0Qg>PK$x4aEub>ULxB&5Ew70iU2Nsroky|Z672I zfWt`n^l9n5va-^7U42X$j)qt&^Vus!`9z3h%sE9kGtmERF*J$8U}9v%tYHQ)1ZN<@ zeJHi GnV`LOD zhW=6zCIbTK{q9C1%=%Zl_wDs52?>d3{Yw{@eGr!=fRN)o$~`Hxr#5w)b61b@GAr5B zLY@E%gSoo5M%<)1wR?j}FHp-8xA+kYf^8J#Q(hjPIK14^jqr#F;LSmlta4o)02C`K z2Qxx80o}2C$AA{V9FEX5)V?dcJbr-E6IsH-#G(sVe2iWN0PdipyfkG#(Az~6Wzr?~ z4zXD9eL;kEP_jYD1_)&pi#rY^LxnlL5*3dDY`6tBH&^d41ET)zS)YV}fB?SKr}+53 zNV3D3g&R<(Yhq1gO@79bpUaF906QcX?GQyid=+T3Dx%P0*d>&z`+{G&LMvXQJ!naY zbRBs7b?sDx*gwyb?+Y*M88uEZyPxYy`Doxa8Xaq*E%0oZ#O!GLshpMUK+Q02>#nBL zedvojiAU+!U{I!`C52(YaVir`kfxI2LPX)y@140Oq$(%P3bvFm=d%wE(0`>x0~m|g z01k8-2g@|V#adpIhjgn#s>Du~7G)&?r`%~nVD62 ! zUzL-}1MM-Q5S$K}Jyek8++6N~P _Rdl`U5_fkiB^3gE$@XmEWz=>%LPkYZ3^J;+dP0LgkHn{NBr zQbc!5KzubWD|d>X)b~m4>SCm&Iv!R>_yGV7pf`z8l_Ov?9AaEJ&EEs0oX5bh#HyH5 zHK~w+0)edbDRu45Ha!t~3Zq9=`766fZd (kWkdBjfXHu|?H4bih6)jX))kuD zL%c=!__@8nEb{W@YyBfUi$ss-WuyH44n1VRN5G!gcZH_kjZdLlx!A<;ctwu%&Y(aa ztM!V{(st~c_21jIOCiq7v%&AfYspaQd%O;u`19mITn@m~J`HuteZ@o>3fOE2oU9%u zJ?Ix0hR0jn{D~@APMZ}uqlP&!X{8VS=?{qovYROp?&&uH^Vx6BSA28X7G$CKlCr@n z&y?&6jeU?*(!HBJX8VW|h&++?*&n1B;z3tJcTApDkjf_@Mj5gIZujF(o{fC6m1M8p zL(kZI;A+rgAdNn>KzUAy6OI6u+1BdtoZPNe)|wfnhRrZ3a6L1U#}*{?Ejn|#+aBKL z%B{hQl&u_u1mF^(FuM69GuAC(iQLyMLIJk~y$-)8H;Rq%SttVwK}El(mhK7-0&N_# z%ChIh%LjLeMR1RjC+cvz#qRsQDky*yW_CuU1#pXn&{w{Z`;Lj<=U(K1(IC!wKg^f% z_}h+Ih=n4;41DepwjPNRz~g^D$knOQ{eq<8kzeAFsszPS{7S6aAOlKekbgqj?qC@E zpvWD*suQlO#zG7tjEmW8gNi%V5yrgeO^p18iP t;bA%7R z_hy7K>3-&w=0)p4^7B6EgstDe!|#=_aU&9Vzo3Ammmfd_ABHiT-32Chg@(eF{~&2A za!9 Hk%lcYQTtk3cj4+c*dq$jN;VCt?HDNi@0`(F1usy_7WqP=v4XJ$LOW#8A1!E=Vhq zWJV-xkh7#L%z4L#j*HC_sQ9Hlpe2FG2{={(z@gRaLbD_~0Bc~K>CFhB1&9u}fcLC* zoD-IkH#c7f5Rso8n=Z)mazHiMHne77v9T-H8r1sE{t;eQEUgKM{g-`Z_P4g+T2SK< ztC)W?s&agM9E4Xg4ir_|+6kJ)+N_C&Sxuyu#({Bk7MkAZX*|lC?7*^T&b%>=3rD?A znhB&p$H4ZpOI@U%ss^1-6p9GR1dPhGL416Ccv94UXQpL3X(=g9X1(DnD;Cf-AQ}Li zw>Nb}t~?9T{jN28M2Z6gLI&eN|DD(N&5FhAlj@T`JERRdGdGVj*Bk`Ww7WR7n8Sf{ zzdVHJD1fbiXrTTNGK6P%`UVDMPbn!kU7J8TKbILlWxIF#JoeAdN#>PU=-}eNnmd;K zqY*VB-!nK+*nx!Ke=WlkUka4qf%o+D6CbPw5XuKsrU7KIgj%yE4h-i#e@m_V;zjSb zZ;u|GwX@BD?TI@lVE1FzB+TTtq^La@BgizjqTebvcc#OlP9JDDzF>HqgZ+vr%3|kg z=;Tdci(PF~(^DQE4Fs>c<58#25g{;QTla5TQwl&KQX4n|^3e904jooM&NEP+sdsS9 zQ1u*EgXF&fP#PN>TTSeE?EM{*@|H}%y?E`LL7_*WvIMZn_{lj|PwjQ78)aX$z1mG` zCHo+^^2aMNVe^_v5hE=uY-Lvr61V-g(ZBnr>te}A%KINPWd#uH6O)r#ik`c(8`J1&Xdq#&o7wqCbvKKkHPX}B!*i|uwE=68 z%5V46s-|ags_m_(j?QO)hTwt =GYv}zy+h-zlC-+P$^8#1J)0n4is@a0N~~F`OnflDdNXp zI$yV^H4sFbkN%_L@}avW5%qE(F>~-}VG^9Ewh0VV 9|CX%K8yr19unHI4ZofmWBbQh5MYQ;Mc3&^ ziTX<$JqFTnm@x1)c%;l{uV(DHL#pf@_0SP chJb9^*bf zhIr8lY2;{8Ctd) 3j7hNcrj8PgEg@ zP=>}%=nLN-P77fpDt;iA=AAgE2SQ4cG*D1Jwy5Y58nSszj{SBODP5D~3Jt*hL|; mcG5F+6TxQ9Z6 z^%na(OMp_2b~VuBAwtT ixG=R>_!mgk#svnU?<=BI{bNTzD_Qin z tyz}_EdbIUC?0R#6_$uu0& =lFvyV-~{6+aByKMYD)Ep)j;OgRmPxO)8-_BAP^L4`Efqb&)~ zf!(uw8$k!PUHTNCB7-ce1Yo1k-~lL sBDURE|gXM3&j>*3Px5gQwvyKz$6=|Hje_dcxM*Yu!mHmW&95`NZO2 z%-_}vG#GMW^lMuHT_JL6m _8^($NE8&O)U+C176; zsI)b-FxfRy(rg`Z#?2dQEM>NIu5L}b3TH%8GVY-~gL#Ypmfj=*_*}7=77hH+AhhK9 z3Vt@s3{{s7uKw%18fH{jj&-TmW9>0AmxZF_2p%q8%Fbn|GUeAI?mqL?)>XzYmbvJ; ziBf{%f)0LAx?k5}E4W-%8{wkIZ0iQ-S$ntv?PO_LNdk&bKg{I8)q~ZFCby3FwLrF0 ztUa(ams8pgOVW~K52$?Z^s}O3Q7-bNX%0wBeQ;3r)MgB@JT!h*2oD-(a3GSs^-*>B zP3? 2>Qbi#M z7Fr&{>;ffVRCu+B_X4yo*gH5X5S-iH&!qZ9hrHPqc)}w7hBe2 Aa*!{9zvwl;bNYDY(*w) zo7!5F2iuGhumD1m4STzpA?LuCBe<3J)NtVeIPn2S`*RsZz(N~OY18mu*yau+ff?8x z;~TThL%Nk-F7hkUbr~T7gGix)lflCZ_RG;(A3xz%LEcs3S=Z&SpO{$!Tf3#4S}?dU z@LW91UJLKWN^2sHPQDB8{^8NI29t3{M%cPHF)8?yyWRq8h}LxRe|{SSlt)0yooFe$ z-MEEZ!_GW}t-x=INsQQJm0L_dlm7SH;ITUv#XoF;+io|~|I)h8*v*aLksytv^{{vQ zat`k#;yT7{OrIMuAjaW6PByFsq*J#o#pjcW*7T0a(P=%vExwkLaG$xjct9V1N@mcw zU4?AOIw?l`+wI)5hUUYLuO-S&e)Nk%h|~e{2551`_V{fL?k@`d@o{AT1)wlux98r2 z%n?C5TkB17xo9$AzW $VX=~#vdC5%M$<@ &)d^GEPLqP7`Yt~3?X-VmA%qK@Z+Sdj z{itv`?gzJr*Jp&kg0xC^y*+JG?HC-p(Dc?g+V(|wN3e;6u$6=}uKcNi@O)7VKT~K} z7*Yz`Y1uWm(t0NS!o4X%qSQ`(1&VYmovYo%zPjQzj@~)-H&tJsEdTx4<6sK*3dyO% zGlWg6=8b&+{w=szD<}W{FQ%6i%!Y|fthho&JeCwAyv+?04lWds^RZ4%=! !2AHZe4eT`onn9y> z-=}>p#wI&HakNEs(|(sbo>_RiI&CLT3>D!N;tW54u|Tc{p)J`v9+#k^)C@R1z`MS# z`vZ2@c)zRLlD!*>?^;{%cOwwBVy^9Uz$s>r_}i^DzpvNfY~vzBAOX0Z*7>Rx#UF?; zKv5cOy^xak^ScS9@e=}RK)K0|*~4dewfbdiRdbG$ulaOJ#@Fl}8!o%LomllIzPl2K z*k~{hrPFKTSqs^-edM--!_MyLzVyz7w?a+2P104F7(%x;(E>UTeUYzHK)hFj=>9WK zY1Z&ucDWav(x8m3_Ze)h0k7F8Uqk%$-*2T^J0345*{@*!OeX&ti-;aQ4DZ{1Txytf zNO5=f-BThk43CiL+w&-4L}?-0{mj-n!kID&5PAsu@ZSxZUFoW8N|RAB5fO*p*w3U{ zcKSWZ`4F;VPCc2#!B|bjGMy6kTwbia;ny#prrVhPr!AS3ID0$)Xm+=6;`k+QT^S!e zs@>6^+;{^Nbcp$R2nG`VAq!KSiK!6mZ)$JP)NFQJ`vtTgUYT4-U`y5hXPO?I1$*-U zVWWxPMo9(-FcpaS;Vq@F2jX)gtL8Y@To<7$;){IW2V>wp3vfbq%m4m0D*r!UGjh{0 ztqe<6W>hSOMu3o6ILd++K*NCYan8|Gh*Gl $q19FtIW$h^iu%BNBr9$gdb?-q^DEstS}ppJd(>uT^vN!o zaK Js2XM5u7SuSr6JJ`;sq_dxOy)r3A+RT1-N~!-Lnd2A@MaYQef`)G z^l#kCo}Pl5!d30>E<+Mj*}sgjzU(roL)e8$PAA|S;IS}IYJ(b1tR7OV2XXQi8)0zN z!D?w~X)gu`Gi)53@Ln?|Xn-!bRcL~Ku!1(g@DN7a*<28}VRVE513{>(Q)Bv5^2*D1 z5u!A|D>QWU=?15T$~{x==|QpkjfX&5@hK@ukeeH}yd0m9@F)><5R?MFZ*RorOh*e; z08dcU*?Ma>%fg=`R;uYpdEVr?HxC3tesfQmnEIxt$x6m3*#{Y!n5-= {uMk)vp=>?LDc<)dmX^=6-d6s#4_s56Qhg4COHlc4p!E$9UcBJNmL6b0wfyN z!J9yo8G$6Us-`9)I=cMpSMr*qw`k65g*7!#=;;Ga%1t_q^EDqzU0hxQ(Gw^)$s;D^ zjKGg{HzD;WwGNBm?iE_FRH|}_spCR>MnZy2rQ*=??ryuwvRr(B*T$GWe2utGzpS9) zBRU-*9m;fK{^ !2ZcE0~qb9X-m zb^5uvxzp`wX|#u7GI4Qnpy&!@3_xq$>b%%ZlTpqOlizqf-NU*nVC5$)A_BxOAl+YA zDjit}9W8zVQ~TiQ?!L3RnLA@!_h)Fx;ivH~U1tW(XG<>Q)|cw)&OpcoO7+7gnLt@0 zXK0un7#P^rb`9tnpm$>D-s>RsR8+CTElEgwdd|Ye21GC!`;T=toWL~e!wWl6QBe2= z1p&+b*%A{IbA!rUO5V+_MyG^-TI3I2cB9q>u)C9!6QHYj>*K?V)x(WN_vP!?O;Ct$ zYipD6zwtd-3G3+SkiA9mr~04Mkub6Y^bVDk+{<{SBQ8A~W6ki*v~6Rs>j6~Xu&}ZF zS6!Dw`RX0#Qsd$l*Ve47CoLFAUB1*76#TupzWgqhk)95Aq3V4e>hEXa Sr=_LUR9ADFwA;_pl#KaSRaJr73ZI`cPNCLTcgbCVPa%Z< zj>BLnzJG`TavKQlKkE|jzyBi(Y??$@4hs*jJ{kirt?Gb3^@7y^^F{1SwQ+DzQC0?O z&;n&fh9LPzpMXYB-^XLZ0!Y$8sTHibsv{Xlr{UE(|I_zzkuPSux<261Ck-aC>m6+2 zfW?uo35@Lerizh!EX#U49u*Sr96`;<5+|>%uFh>YQHpp;(djAX{Q$6Q^w0R%*dshN zpnpN51ACbe+a-bayr$+5ljG^KL;)q?%yI2r21nC#SvD)x^X^Su_pS#NFLpAWaxd z;_`HJ!^FjPvbX;t;(vX?&%-l3ItofXDL_+ob#Yv>{}R~FM-UWGPK+yGJp 8G3HbM0DtKv=OjJn`@ZLU-agOgIVIy0 z`!^vKO-}OQ4@E^q)e~#K%?1&?^74BiNSZ>K!vfu-be%`d$^c1zNGfGUTv8~0_^I&3 ziGA`ZJky2+@oQ KOOmMEQE`=a4v8HEc z@S*Lw{OLGf=8q7HOeWKYL<%_*>JC;eQ`V*SN 1YB^vW`NNLfRbFwnaC^I} {=Wd^RHk*yyFWfOy z8w-(c2VOe%zg++U76o`b0%Y(7$2E(~$ImY$P_0z1kEIucQ$QqQ#4SkE!l7w~@7wV? zspeRhR=0eN)}jlj<}MnIXSO!%mQg94wR@rkwT57a)G%nxumj~*m~zk>DTQ$XjrVbB z$D>7D?;jw#_ gvg1c5E*Wbx-Wh&I5) 7)3$0y=9kzMc3JerZw+SxfOvZdpW zyu)hQi=X#WSa(e#^M|^+X COycRlQE5>bc9vu91Te9BM!{(RL+badB~RfUgWCWh5p}udIhKDvFOESJK*{vLb3z zPsE$p@M|YABh+%aJpD3I7mN#Tj?B})XX6=oAT*XwUFAoY35B;yURd&eg48CkH=rqb z$^DH;UR-x6`}=Pi42HS6cTfZ