furrr/0000755000177700017770000000000013277053215012751 5ustar herbrandtherbrandtfurrr/tests/0000755000177700017770000000000013274704111014106 5ustar herbrandtherbrandtfurrr/tests/testthat.R0000644000177700017770000000006613274704111016073 0ustar herbrandtherbrandtlibrary(testthat) library(furrr) test_check("furrr") furrr/tests/testthat/0000755000177700017770000000000013277053215015753 5ustar herbrandtherbrandtfurrr/tests/testthat/test-future-options.R0000644000177700017770000000331413275114110022045 0ustar herbrandtherbrandtcontext("test-future-options.R") # ------------------------------------------------------------------------------ # Setup .th <- retrieve_test_helpers() test_msg <- .th$test_msg test_dat <- .th$test_dat # ------------------------------------------------------------------------------ # Testing for(.e in .th$executors) { # Don't test multicore on non-Mac if(.e == "multicore" && .th$system.os != "Darwin") { next } plan(.e, substitute = FALSE) # Selective variable/package exports only works on multisession # On multicore, it sees the variables by forked process shared memory if(.e == "multisession") { # Init x/y outside the test environment, otherwise seen by multisession x <- 1 y <- 1 test_that(test_msg(.e, "selective exporting of variables works"), { # This should result in an error, export y not x #expect_error(future_map(1, ~x, .options = future_options(globals = "y"))) # This works, export x, need x expect_equal(future_map(1, ~x, .options = future_options(globals = "x")), list(1)) }) test_that(test_msg(.e, "selective exporting of packages works"), { # This should result in an error, export purrr not dplyr expect_error(future_map(1, ~tibble(x = 1), .options = future_options(packages = "purrr"))) # This works, export dplyr, need dplyr expect_equal(future_map(1, ~tibble(x = 1), .options = future_options(packages = "dplyr")), list(dplyr::tibble(x = 1))) }) } test_that(test_msg(.e, "setting seed keeps reproducible numbers"), { opts <- future_options(seed = 1L) expect_equal(future_map(1, runif, .options = opts), future_map(1, runif, .options = opts)) }) } furrr/tests/testthat/test-progress.R0000644000177700017770000000211713274714424020723 0ustar herbrandtherbrandtcontext("test-progress.R") # ------------------------------------------------------------------------------ # Setup .th <- retrieve_test_helpers() test_msg <- .th$test_msg test_dat <- .th$test_dat # ------------------------------------------------------------------------------ # Testing for(.e in .th$executors) { # Don't test multicore on non-Mac if(.e == "multicore" && .th$system.os != "Darwin") { next } # No progress on sequential if(.e == "sequential") { next } plan(.e, substitute = FALSE) test_that(test_msg(.e, "Progress bar is emitted on long running tasks"), { output <- capture.output( .furrr <- furrr::future_map(test_dat, ~Sys.sleep(runif(1, 2, 3)), .progress = TRUE) ) # Is "Progress" in the output text? expect_true(grepl("Progress", output[1])) }) test_that(test_msg(.e, "Progress bar is not emitted on short running tasks"), { output <- capture.output( .furrr <- furrr::future_map(test_dat, ~.x, .progress = TRUE) ) # Is "Progress" in the output text? expect_false(grepl("Progress", output[1])) }) } furrr/tests/testthat/test-map2.R0000644000177700017770000000227213274715024017715 0ustar herbrandtherbrandtcontext("test-map2.R") # ------------------------------------------------------------------------------ # Setup .th <- retrieve_test_helpers() test_msg <- .th$test_msg test_dat <- .th$test_dat test_dat2 <- test_dat # ------------------------------------------------------------------------------ # Testing for(.e in .th$executors) { # Don't test multicore on non-Mac if(.e == "multicore" && .th$system.os != "Darwin") { next } plan(.e, substitute = FALSE) test_that(test_msg(.e, "equivalence with map2()"), { .f <- identical .purrr <- purrr::map2(test_dat, test_dat2, .f) .furrr <- furrr::future_map2(test_dat, test_dat2, .f) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "equivalence with vector map2()s"), { .f <- ~as.character(sum(.x, .y)) .purrr <- purrr::map2_chr(test_dat, test_dat2, .f) .furrr <- furrr::future_map2_chr(test_dat, test_dat2, .f) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "equivalence with df map2()s"), { .f <- ~data.frame(x = sum(.x, .y)) .purrr <- purrr::map2_dfr(test_dat, test_dat2, .f) .furrr <- furrr::future_map2_dfr(test_dat, test_dat2, .f) expect_equal(.purrr, .furrr) }) } furrr/tests/testthat/test-map.R0000644000177700017770000000374413276343210017634 0ustar herbrandtherbrandtcontext("test-map.R") # ------------------------------------------------------------------------------ # Setup .th <- retrieve_test_helpers() test_msg <- .th$test_msg test_dat <- .th$test_dat # ------------------------------------------------------------------------------ # Testing for(.e in .th$executors) { # Don't test multicore on non-Mac if(.e == "multicore" && .th$system.os != "Darwin") { next } plan(.e, substitute = FALSE) test_that(test_msg(.e, "equivalence with map()"), { .f <- class .purrr <- purrr::map(test_dat, .f) .furrr <- furrr::future_map(test_dat, .f) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "equivalence with vector map()s"), { .f <- class .purrr <- purrr::map_chr(test_dat, .f) .furrr <- furrr::future_map_chr(test_dat, .f) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "equivalence with df map()s"), { .f <- ~data.frame(x = class(.x)) .purrr <- purrr::map_dfr(test_dat, .f) .furrr <- furrr::future_map_dfr(test_dat, .f) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "equivalence with map_at()"), { .f <- class .purrr <- purrr::map_at(test_dat, 1, .f) .furrr <- furrr::future_map_at(test_dat, 1, .f) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "equivalence with map_if()"), { .f <- class .purrr <- purrr::map_if(test_dat, ~.x == 1, .f) .furrr <- furrr::future_map_if(test_dat, ~.x == 1, .f) expect_equal(.purrr, .furrr) }) # See issue #7 test_that(test_msg(.e, "Working mutate+map double nest with ~"), { skip_on_cran() deep_list <- dplyr::tibble( deep_nest = list( list( list(a = 4), list(b = 6) ) ) ) res <- dplyr::mutate( .data = deep_list, mod_nest = future_map( .x = deep_nest, .f = ~{ x <- .x purrr::map(x, ~.x) }) ) expect_equal(rlang::squash_dbl(res$mod_nest), c(a = 4, b = 6)) }) } furrr/tests/testthat/test-pmap.R0000644000177700017770000000414513276314335020016 0ustar herbrandtherbrandtcontext("test-pmap.R") # ------------------------------------------------------------------------------ # Setup .th <- retrieve_test_helpers() test_msg <- .th$test_msg test_dat <- .th$test_dat test_l <- list(test_dat, test_dat) # ------------------------------------------------------------------------------ # Testing for(.e in .th$executors) { # Don't test multicore on non-Mac if(.e == "multicore" && .th$system.os != "Darwin") { next } plan(.e, substitute = FALSE) test_that(test_msg(.e, "equivalence with pmap()"), { .f <- identical .purrr <- purrr::pmap(test_l, sum) .furrr <- future_pmap(test_l, sum) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "equivalence with vector pmap()s"), { .f <- ~as.character(sum(.x, .y)) .purrr <- purrr::pmap_chr(test_l, .f) .furrr <- future_pmap_chr(test_l, .f) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "equivalence with df pmap()s"), { .f <- ~data.frame(x = sum(.x, .y)) .purrr <- purrr::pmap_dfr(test_l, .f) .furrr <- future_pmap_dfr(test_l, .f) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "named arguments can be passed through"), { vec_mean <- function(.x, .y, na.rm = FALSE) { mean(c(.x,.y), na.rm = na.rm) } test_l_na <- test_l test_l_na[[1]][1] <- NA .purrr <- purrr::pmap(test_l_na, vec_mean, na.rm = TRUE) .furrr <- future_pmap(test_l_na, vec_mean, na.rm = TRUE) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "arguments can be matched by name"), { test_l_named <- test_l names(test_l_named) <- c("x", "y") test_l_named[["y"]] <- test_l_named[["y"]] * 2 .f <- function(y, x) {y - x} .purrr <- purrr::pmap(test_l_named, .f) .furrr <- future_pmap(test_l_named, .f) expect_equal(.purrr, .furrr) }) test_that(test_msg(.e, "unused components can be absorbed"), { .f_bad <- function(x) {x} .f <- function(x, ...) {x} .purrr <- purrr::pmap(test_l, .f) .furrr <- future_pmap(test_l, .f) expect_error(future_pmap(test_l, .f_bad)) expect_equal(.purrr, .furrr) }) } furrr/tests/testthat/helper-test-setup.R0000644000177700017770000000061213274714304021467 0ustar herbrandtherbrandtretrieve_test_helpers <- function() { # Take advantage of scoping test_msg <- function(executor, ...) { paste(executor, ..., sep = " - ") } executors <- c("sequential", "multisession", "multicore") system.os <- Sys.info()[["sysname"]] test_dat <- seq_len(4) list( test_msg = test_msg, executors = executors, system.os = system.os, test_dat = test_dat ) } furrr/NAMESPACE0000644000177700017770000000314413275143501014166 0ustar herbrandtherbrandt# Generated by roxygen2: do not edit by hand S3method(future_modify,default) S3method(future_modify_at,default) S3method(future_modify_if,default) export(future_imap) export(future_imap_chr) export(future_imap_dbl) export(future_imap_dfc) export(future_imap_dfr) export(future_imap_int) export(future_imap_lgl) export(future_invoke_map) export(future_invoke_map_chr) export(future_invoke_map_dbl) export(future_invoke_map_dfc) export(future_invoke_map_dfr) export(future_invoke_map_int) export(future_invoke_map_lgl) export(future_map) export(future_map2) export(future_map2_chr) export(future_map2_dbl) export(future_map2_dfc) export(future_map2_dfr) export(future_map2_int) export(future_map2_lgl) export(future_map_at) export(future_map_chr) export(future_map_dbl) export(future_map_dfc) export(future_map_dfr) export(future_map_if) export(future_map_int) export(future_map_lgl) export(future_modify) export(future_modify_at) export(future_modify_if) export(future_options) export(future_pmap) export(future_pmap_chr) export(future_pmap_dbl) export(future_pmap_dfc) export(future_pmap_dfr) export(future_pmap_int) export(future_pmap_lgl) importFrom(future,as.FutureGlobals) importFrom(future,future) importFrom(future,getGlobalsAndPackages) importFrom(future,nbrOfWorkers) importFrom(future,resolve) importFrom(future,values) importFrom(globals,cleanup) importFrom(globals,globalsByName) importFrom(parallel,nextRNGStream) importFrom(parallel,nextRNGSubStream) importFrom(parallel,splitIndices) importFrom(purrr,list_along) importFrom(purrr,set_names) importFrom(rlang,"%||%") importFrom(utils,capture.output) importFrom(utils,str) furrr/NEWS.md0000644000177700017770000000152613275144257014060 0ustar herbrandtherbrandt# furrr 0.1.0 Features: * `future_pmap_*()` functions have been added to mirror `pmap()`. * The `future.*` arguments to each function have been replaced with an overarching `.options` argument. Use `future_options()` to create a set of options suitable to be passed to `.options`. This change streamlines the interface greatly, and simplifies documentation (#8, @hadley). * `future_invoke_map_*()` functions have been added to mirror `invoke_map()`. * More documentation and examples have been added. * Added the ability to use a progress bar with `.progress = TRUE` for multicore, multiprocess, and multisession `plan()`s. Bug Fixes: * Fixed a bug with using `~` inside a `mutate()` + `map()` combination. * Added a missed `future_imap_int()`. # furrr 0.0.0 * Original GitHub release of `furrr` on 2018-04-13. furrr/R/0000755000177700017770000000000013275126736013161 5ustar herbrandtherbrandtfurrr/R/fold.R0000644000177700017770000000474513274650566014244 0ustar herbrandtherbrandt#' Efficient fold / reduce / accumulate / combine of a vector #' #' This function comes from the `future.apply` package. #' #' @param x A vector. #' #' @param f A binary function, i.e. a function take takes two arguments. #' #' @param left If `TRUE`, vector is combined from the left (the first element), #' otherwise the right (the last element). #' #' @param unname If `TRUE`, function `f` is called as `f(unname(y), x[[ii]])`, #' otherwise as `f(y, x[[ii]])`, which may introduce name `"y"`. #' #' @param threshold An integer (>= 2) specifying the length where the #' recursive divide'and'conquer call will stop and incremental building of #' the partial value is performed. Using `threshold = +Inf` will disable #' recursive folding. #' #' @return A vector. #' #' @details #' In order for recursive folding to give the same results as non-recursive #' folding, binary function `f` must be _associative_ with itself, i.e. #' `f(f(x[[1]], x[[2]]), x[[3]])` equals `f(x[[1]], f(x[[2]]), x[[3]])`. #' #' This function is a more efficient (memory and speed) of #' [`base::Reduce(f, x, right = !left, accumulate = FALSE)`][base::Reduce], #' especially when `x` is long. fold <- function(x, f, left = TRUE, unname = TRUE, threshold = 1000L) { f <- match.fun(f) n <- length(x) if (n == 0L) return(NULL) if (!is.vector(x) || is.object(x)) x <- as.list(x) if (n == 1L) return(x[[1]]) stopifnot(length(left) == 1, is.logical(left), !is.na(left)) stopifnot(length(threshold) == 1, is.numeric(threshold), !is.na(threshold), threshold >= 2) if (n >= threshold) { ## Divide and conquer, i.e. split, build the two parts, and merge n_mid <- n %/% 2 y_left <- Recall(f = f, x = x[ 1:n_mid], left = left, threshold = threshold) y_right <- Recall(f = f, x = x[(n_mid+1L):n], left = left, threshold = threshold) y <- f(y_left, y_right) y_left <- y_right <- NULL } else { ## Incrementally build result vector if (left) { y <- x[[1L]] if (unname) { for (ii in 2:n) y <- forceAndCall(n = 2L, FUN = f, unname(y), x[[ii]]) } else { for (ii in 2:n) y <- forceAndCall(n = 2L, FUN = f, y, x[[ii]]) } } else { y <- x[[n]] if (unname) { for (ii in (n-1):1) y <- forceAndCall(n = 2L, FUN = f, x[[ii]], unname(y)) } else { for (ii in (n-1):1) y <- forceAndCall(n = 2L, FUN = f, x[[ii]], y) } } } y } furrr/R/future_map.R0000644000177700017770000001116513276355457015464 0ustar herbrandtherbrandt#' Apply a function to each element of a vector via futures #' #' These functions work exactly the same as [purrr::map()] functions, but allow #' you to run the map in parallel. The documentation is #' adapted from both `purrr::map()`, and `future.apply::future_lapply()`, #' so look there for more details. #' #' @inheritParams purrr::map #' #' @param .progress A logical, for whether or not to print a progress bar for #' multiprocess, multisession, and multicore plans. #' #' @param .options The `future` specific options to use with the workers. This must #' be the result from a call to [future_options()]. #' #' @return #' All functions return a vector the same length as `.x`. #' #' [future_map()] returns a list, [future_map_lgl()] a logical vector, #' [future_map_int()] an integer vector, [future_map_dbl()] a double vector, #' and [future_map_chr()] a character vector. #' The output of `.f` will be automatically typed upwards, #' e.g. logical -> integer -> double -> character. #' #' #' @examples #' #' library(furrr) #' library(dplyr) # for the pipe #' #' \donttest{ #' plan(multiprocess) #' } #' #' 1:10 %>% #' future_map(rnorm, n = 10) %>% #' future_map_dbl(mean) #' #' # If each element of the output is a data frame, use #' # future_map_dfr to row-bind them together: #' mtcars %>% #' split(.$cyl) %>% #' future_map(~ lm(mpg ~ wt, data = .x)) %>% #' future_map_dfr(~ as.data.frame(t(as.matrix(coef(.))))) #' #' # You can be explicit about what gets exported to the workers #' #' # To see this, use multisession (NOT multicore if on a Mac as the forked workers #' # still have access to this environment) #' \donttest{ #' plan(multisession) #' } #' x <- 1 #' y <- 2 #' #' # This will fail, y is not exported (no black magic occurs) #' # future_map(1, ~y, .options = future_options(globals = "x")) #' #' # y is exported #' future_map(1, ~y, .options = future_options(globals = "y")) #' #' #' @export future_map <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map_template(purrr::map, "list", .x, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map #' @export future_map_chr <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map_template(purrr::map_chr, "character", .x, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map #' @export future_map_dbl <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map_template(purrr::map_dbl, "double", .x, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map #' @export future_map_int <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map_template(purrr::map_int, "integer", .x, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map #' @export future_map_lgl <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map_template(purrr::map_lgl, "logical", .x, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map #' @export future_map_dfr <- function(.x, .f, ..., .id = NULL, .progress = FALSE, .options = future_options()) { # Passing through the template doesn't work because of the way fold() works. # Could parameterize around fold(res, ___), but this is easier if (!rlang::is_installed("dplyr")) { rlang::abort("`future_map_dfr()` requires dplyr") } res <- future_map(.x, .f, ..., .progress = .progress, .options = .options) dplyr::bind_rows(res, .id = .id) } #' @rdname future_map #' @export future_map_dfc <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { # Passing through the template doesn't work because of the way fold() works. # Could parameterize around fold(res, ___), but this is easier if (!rlang::is_installed("dplyr")) { rlang::abort("`future_map_dfc()` requires dplyr") } res <- future_map(.x, .f, ..., .progress = .progress, .options = .options) dplyr::bind_cols(res) } #' @rdname future_map #' @export #' @importFrom purrr list_along set_names future_map_if <- function(.x, .p, .f, ..., .progress = FALSE, .options = future_options()) { sel <- probe(.x, .p) out <- list_along(.x) out[sel] <- future_map(.x[sel], .f, ..., .progress = .progress, .options = .options) out[!sel] <- .x[!sel] set_names(out, names(.x)) } #' @rdname future_map #' @export #' @importFrom purrr list_along set_names future_map_at <- function(.x, .at, .f, ..., .progress = FALSE, .options = future_options()) { sel <- inv_which(.x, .at) out <- list_along(.x) out[sel] <- future_map(.x[sel], .f, ..., .progress = .progress, .options = .options) out[!sel] <- .x[!sel] set_names(out, names(.x)) } furrr/R/utils.R0000644000177700017770000001323213275121317014433 0ustar herbrandtherbrandt#' @importFrom rlang %||% # ------------------------------------------------------------------------------ # Unexported functions from purrr probe <- function(.x, .p, ...) { if (rlang::is_logical(.p)) { stopifnot(length(.p) == length(.x)) .p } else { purrr::map_lgl(.x, .p, ...) } } inv_which <- function(x, sel) { if (is.character(sel)) { names <- names(x) if (is.null(names)) { stop("character indexing requires a named object", call. = FALSE) } names %in% sel } else if (is.numeric(sel)) { seq_along(x) %in% sel } else { stop("unrecognised index type", call. = FALSE) } } vec_index <- function(x){ names(x) %||% seq_along(x) } as_invoke_function <- function(f) { if (is.function(f)) { list(f) } else { f } } # ------------------------------------------------------------------------------ # util # Early abort from mapping function # To stay consistent with purrr::map_*() return types # purrr handles this at the C level get_zero_length_type <- function(.type) { switch( .type, "character" = character(), "double" = double(), "list" = list(), "integer" = integer(), "logical" = logical() ) } # Catch a NULL pointer that can result from serialization of the rlang::`~` is_bad_rlang_tilde <- function(tilde) { # Attempt to call the tilde. # If bad, get a "NULL value passed as symbol address" error tilde_msg <- tryCatch( expr = { tilde() "no issues" }, error = function(e) { e } ) if(tilde_msg != "no issues") { is_bad <- TRUE } else { is_bad <- FALSE } is_bad } # ------------------------------------------------------------------------------ # from future.apply ## From R.utils 2.0.2 (2015-05-23) hpaste <- function(..., sep = "", collapse = ", ", lastCollapse = NULL, maxHead = if (missing(lastCollapse)) 3 else Inf, maxTail = if (is.finite(maxHead)) 1 else Inf, abbreviate = "...") { if (is.null(lastCollapse)) lastCollapse <- collapse # Build vector 'x' x <- paste(..., sep = sep) n <- length(x) # Nothing todo? if (n == 0) return(x) if (is.null(collapse)) return(x) # Abbreviate? if (n > maxHead + maxTail + 1) { head <- x[seq_len(maxHead)] tail <- rev(rev(x)[seq_len(maxTail)]) x <- c(head, abbreviate, tail) n <- length(x) } if (!is.null(collapse) && n > 1) { if (lastCollapse == collapse) { x <- paste(x, collapse = collapse) } else { xT <- paste(x[1:(n-1)], collapse = collapse) x <- paste(xT, x[n], sep = lastCollapse) } } x } # hpaste() mdebug <- function(...) { if (!getOption("future.debug", FALSE)) return() message(sprintf(...)) } ## mdebug() ## When 'default' is specified, this is 30x faster than ## base::getOption(). The difference is that here we use ## use names(.Options) whereas in 'base' names(options()) ## is used. getOption <- local({ go <- base::getOption function(x, default = NULL) { if (missing(default) || match(x, table = names(.Options), nomatch = 0L) > 0L) go(x) else default } }) ## getOption() get_random_seed <- function() { env <- globalenv() env$.Random.seed } set_random_seed <- function(seed) { env <- globalenv() if (is.null(seed)) { rm(list = ".Random.seed", envir = env, inherits = FALSE) } else { env$.Random.seed <- seed } } next_random_seed <- function(seed = get_random_seed()) { sample.int(n = 1L, size = 1L, replace = FALSE) seed_next <- get_random_seed() stopifnot(!any(seed_next != seed)) invisible(seed_next) } is_valid_random_seed <- function(seed) { oseed <- get_random_seed() on.exit(set_random_seed(oseed)) env <- globalenv() env$.Random.seed <- seed res <- tryCatch({ sample.int(n = 1L, size = 1L, replace = FALSE) }, simpleWarning = function(w) w) !inherits(res, "simpleWarning") } is_lecyer_cmrg_seed <- function(seed) { is.numeric(seed) && length(seed) == 7L && all(is.finite(seed)) && seed[1] == 407L } # @importFrom utils capture.output as_lecyer_cmrg_seed <- function(seed) { ## Generate a L'Ecuyer-CMRG seed (existing or random)? if (is.logical(seed)) { stopifnot(length(seed) == 1L) if (!is.na(seed) && !seed) { stop("Argument 'seed' must be TRUE if logical: ", seed) } oseed <- get_random_seed() ## Already a L'Ecuyer-CMRG seed? Then use that as is. if (!is.na(seed) && seed) { if (is_lecyer_cmrg_seed(oseed)) return(oseed) } ## Otherwise, generate a random one. on.exit(set_random_seed(oseed), add = TRUE) RNGkind("L'Ecuyer-CMRG") return(get_random_seed()) } stopifnot(is.numeric(seed), all(is.finite(seed))) seed <- as.integer(seed) ## Already a L'Ecuyer-CMRG seed? if (length(seed) == 7L) { if (seed[1] != 407L) { stop("Argument 'seed' must be L'Ecuyer-CMRG RNG seed as returned by parallel::nextRNGStream() or an single integer: ", capture.output(str(seed))) } return(seed) } ## Generate a new L'Ecuyer-CMRG seed? if (length(seed) == 1L) { oseed <- get_random_seed() on.exit(set_random_seed(oseed), add = TRUE) RNGkind("L'Ecuyer-CMRG") set.seed(seed) return(get_random_seed()) } stop("Argument 'seed' must be of length 1 or 7 (= 1+6):", capture.output(str(seed))) } import_from <- function(name, default = NULL, package) { ns <- getNamespace(package) if (exists(name, mode = "function", envir = ns, inherits = FALSE)) { get(name, mode = "function", envir = ns, inherits = FALSE) } else if (!is.null(default)) { default } else { stop(sprintf("No such '%s' function: %s()", package, name)) } } import_future <- function(name, default = NULL) { import_from(name, default = default, package = "future") } furrr/R/resolve.R0000644000177700017770000000112713275131370014752 0ustar herbrandtherbrandtmulti_resolve <- function(fs, nms = NULL) { nchunks <- length(fs) debug <- getOption("future.debug", FALSE) ## Resolving futures if (debug) mdebug("Resolving %d futures (chunks) ...", nchunks) values <- values(fs) if (debug) mdebug("Resolving %d futures (chunks) ... DONE", nchunks) ## Not needed anymore rm(list = "fs") ## Fold result together if (debug) mdebug("Reducing values from %d chunks ...", nchunks) values <- fold(values, c) if(!is.null(nms)) { names(values) <- nms } if (debug) mdebug("Reducing values from %d chunks ... DONE", nchunks) values } furrr/R/random-seeds.R0000644000177700017770000000507713274650566015700 0ustar herbrandtherbrandtgenerate_seed_streams <- function(seed, n_seeds) { debug <- getOption("future.debug", FALSE) ## A pregenerated sequence of random seeds? if (is.list(seed)) { if (debug) mdebug("Using a pre-define stream of random seeds ...", n_seeds) nseed <- length(seed) if (nseed != n_seeds) { stop("Argument 'seed' is a list, which specifies the sequence of seeds to be used for each element in '.x', but length(seed) != length(.x): ", nseed, " != ", n_seeds) } ## Assert same type of RNG seeds? ns <- unique(unlist(lapply(seed, FUN = length), use.names = FALSE)) if (length(ns) != 1) { stop("The elements of the list specified in argument 'seed' are not all of the same lengths (did you really pass RNG seeds?): ", hpaste(ns)) } ## Did use specify scalar integers as meant for set.seed()? if (ns == 1L) { stop("Argument 'seed' is invalid. Pre-generated random seeds must be valid .Random.seed seeds, which means they should be all integers and consists of two or more elements, not just one.") } types <- unlist(lapply(seed, FUN = typeof), use.names = FALSE) if (!all(types == "integer")) { stop("The elements of the list specified in argument 'seed' are not all integers (did you really pass RNG seeds?): ", hpaste(unique(types))) } ## Check if valid random seeds are specified. ## For efficiency, only look at the first one. if (!is_valid_random_seed(seed[[1]])) { stop("The list in argument 'seed' does not seem to hold elements that are valid .Random.seed values: ", capture.output(str(seeds[[1]]))) } seeds <- seed if (debug) mdebug("Using a pre-define stream of random seeds ... DONE", n_seeds) } else { if (debug) mdebug("Generating random seed streams for %d elements ...", n_seeds) ## Generate sequence of _all_ RNG seeds starting with an initial seed ## '.seed' that is based on argument 'seed'. .seed <- as_lecyer_cmrg_seed(seed) seeds <- vector("list", length = n_seeds) for (ii in seq_len(n_seeds)) { ## RNG substream seed used in call FUN(.x[[ii]], ...): ## This way each future can in turn generate further seeds, also ## recursively, with minimal risk of generating the same seeds as ## another future. This should make it safe to recursively call ## future_lapply(). /HB 2017-01-11 seeds[[ii]] <- nextRNGSubStream(.seed) ## Main random seed for next iteration (= ii + 1) .seed <- nextRNGStream(.seed) } if (debug) mdebug("Generating random seed streams for %d elements ... DONE", n_seeds) } seeds } furrr/R/progress.R0000644000177700017770000000375513276077515015163 0ustar herbrandtherbrandtpoll_progress <- function(fs, temp_file, rule_max_width) { debug <- getOption("future.debug", FALSE) if (debug) mdebug("Polling for progress ...") not_resolved_once <- !all_resolved(fs) # Poll the files until all the jobs are complete while (!all_resolved(fs)) { # -1 because of empty tick needed to init file. # Otherwise if we get here too quickly it gives error temp_file_con <- file(temp_file, "r") n_ticks <- length(readLines(temp_file_con)) - 1 close(temp_file_con) max_width <- console_width() progress_width <- 10 finish_width <- 5 carriage_width <- 1 filler_width <- max_width - progress_width - finish_width - carriage_width rule_width <- floor(filler_width * n_ticks / rule_max_width) space_width <- filler_width - rule_width spaces <- paste0(rep(" ", times = space_width), collapse = "") # The one line - symbol came from cli::symbols$line progress <- paste0(rep("\u2500", times = rule_width), collapse = "") all_text <- paste0("Progress: ", progress, spaces, " 100%") cat("\r", all_text) utils::flush.console() } if(not_resolved_once) { # Separate progress from output max_width <- console_width() progress_width <- 10 finish_width <- 5 carriage_width <- 1 filler_width <- max_width - progress_width - finish_width - carriage_width progress <- paste0(rep("\u2500", times = filler_width), collapse = "") all_text <- paste0("Progress: ", progress, " 100%") cat("\r", all_text) cat("\n\n") } if (debug) mdebug("Polling for progress ... DONE") } # Needed for progress updates update_progress <- function (file) { progress_text <- sprintf("tick\n") cat(progress_text, file = file, append = TRUE) } # Needed for progress updates all_resolved <- function (futures) { each_resolved <- vapply(futures, future::resolved, FALSE) all(each_resolved) } console_width <- function() { width <- Sys.getenv("RSTUDIO_CONSOLE_WIDTH", getOption("width", 80)) as.integer(width) } furrr/R/global-gatherer.R0000644000177700017770000001117613275650352016345 0ustar herbrandtherbrandtgather_globals_and_packages <- function(.options, .map, .f, .progress, envir, ...) { debug <- getOption("future.debug", FALSE) objectSize <- import_future("objectSize") ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 1. Global variables ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## The default is to gather globals if (is.null(.options$globals)) .options$globals <- TRUE packages <- NULL globals <- .options$globals if (is.logical(globals)) { ## Gather all globals? if (globals) { if (debug) mdebug("Finding globals ...") expr <- do.call(call, args = c(list(".f"), list(...))) gp <- getGlobalsAndPackages(expr, envir = envir, globals = TRUE) globals <- gp$globals packages <- gp$packages gp <- NULL if (debug) { mdebug(" - globals found: [%d] %s", length(globals), hpaste(sQuote(names(globals)))) mdebug(" - needed namespaces: [%d] %s", length(packages), hpaste(sQuote(packages))) mdebug("Finding globals ... DONE") } } else { ## globals = FALSE globals <- c(".f", names(list(...)), "...") globals <- globalsByName(globals, envir = envir, mustExist = FALSE) } } else if (is.character(globals)) { globals <- unique(c(globals, ".f", names(list(...)), "...")) globals <- globalsByName(globals, envir = envir, mustExist = FALSE) } else if (is.list(globals)) { names <- names(globals) if (length(globals) > 0 && is.null(names)) { stop("Invalid argument '.options$globals'. All globals must be named.") } } else { stop("Invalid argument '.options$globals': ", mode(globals)) } globals <- as.FutureGlobals(globals) stopifnot(inherits(globals, "FutureGlobals")) names <- names(globals) if (!is.element(".f", names)) { globals <- c(globals, .f = .f) } # The purrr function that gets used must be passed as a global # This mainly affects multisession if (!is.element(".map", names)) { globals <- c(globals, .map = .map) } if (!is.element("...", names)) { if (debug) mdebug("Getting '...' globals ...") dotdotdot <- globalsByName("...", envir = envir, mustExist = TRUE) dotdotdot <- as.FutureGlobals(dotdotdot) dotdotdot <- resolve(dotdotdot) attr(dotdotdot, "total_size") <- objectSize(dotdotdot) if (debug) mdebug("Getting '...' globals ... DONE") globals <- c(globals, dotdotdot) } ## Assert there are no reserved variables names among globals reserved <- intersect(c("...future.f", "...future.x_ii", "...future.y_ii", # technically only needed for map2, but this is okay "...future.seeds_ii"), names) if (length(reserved) > 0) { stop("Detected globals using reserved variables names: ", paste(sQuote(reserved), collapse = ", ")) } ## Avoid .f() clash with map(.x, .f, ...) below. names <- names(globals) names[names == ".f"] <- "...future.f" names[names == ".map"] <- "...future.map" # add the map function names(globals) <- names if (debug) { mdebug("Globals to be used in all futures:") mdebug(paste(capture.output(str(globals)), collapse = "\n")) } ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 2. Packages ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # purrr is always included packages <- unique(c(packages, "purrr")) if (!is.null(.options$packages)) { .options$packages <- unique(.options$packages) stopifnot(!anyNA(.options$packages), all(nzchar(.options$packages))) packages <- unique(c(packages, .options$packages)) } if (debug) { mdebug("Packages to be attached in all futures:") mdebug(paste(capture.output(str(packages)), collapse = "\n")) } ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 3. Progress ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # .progress always needs to be on the workers for the if statement... globals <- c(globals, .progress = .progress) # ...but we add the tempfile and the function if .progress = TRUE if(.progress) { temp_file <- tempfile(fileext = ".txt") writeLines("falsetick", temp_file) globals <- c(globals, update_progress = update_progress, temp_file = temp_file) } ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 3. Extra functions required on the workers ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - globals <- c(globals, is_bad_rlang_tilde = is_bad_rlang_tilde) .options$packages <- packages .options$globals <- globals .options } furrr/R/future_invoke_map.R0000644000177700017770000000605313276355441017030 0ustar herbrandtherbrandt#' Invoke functions via futures #' #' These functions work exactly the same as [purrr::invoke_map()] functions, but allow #' you to invoke in parallel. #' #' @inheritParams purrr::invoke_map #' @inheritParams future_map #' #' @param .f A list of functions. #' @param .x A list of argument-lists the same length as `.f` (or length 1). #' The default argument, `list(NULL)`, will be recycled to the same length as #' `.f`, and will call each function with no arguments #' (apart from any supplied in `...`.) #' #' @examples #' #' \donttest{ #' plan(multiprocess) #' } #' #' df <- dplyr::tibble( #' f = c("runif", "rpois", "rnorm"), #' params = list( #' list(n = 10), #' list(n = 5, lambda = 10), #' list(n = 10, mean = -3, sd = 10) #' ) #' ) #' #' future_invoke_map(df$f, df$params) #' #' #' @export #' future_invoke_map <- function(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) { .env <- .env %||% parent.frame() .f <- as_invoke_function(.f) future_map2(.f, .x, purrr::invoke, ..., .env = .env, .progress = .progress, .options = .options) } #' @rdname future_invoke_map #' @export future_invoke_map_chr <- function(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) { .env <- .env %||% parent.frame() .f <- as_invoke_function(.f) future_map2_chr(.f, .x, purrr::invoke, ..., .env = .env, .progress = .progress, .options = .options) } #' @rdname future_invoke_map #' @export future_invoke_map_dbl <- function(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) { .env <- .env %||% parent.frame() .f <- as_invoke_function(.f) future_map2_dbl(.f, .x, purrr::invoke, ..., .env = .env, .progress = .progress, .options = .options) } #' @rdname future_invoke_map #' @export future_invoke_map_int <- function(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) { .env <- .env %||% parent.frame() .f <- as_invoke_function(.f) future_map2_int(.f, .x, purrr::invoke, ..., .env = .env, .progress = .progress, .options = .options) } #' @rdname future_invoke_map #' @export future_invoke_map_lgl <- function(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) { .env <- .env %||% parent.frame() .f <- as_invoke_function(.f) future_map2_lgl(.f, .x, purrr::invoke, ..., .env = .env, .progress = .progress, .options = .options) } #' @rdname future_invoke_map #' @export future_invoke_map_dfr <- function(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) { .env <- .env %||% parent.frame() .f <- as_invoke_function(.f) future_map2_dfr(.f, .x, purrr::invoke, ..., .env = .env, .progress = .progress, .options = .options) } #' @rdname future_invoke_map #' @export future_invoke_map_dfc <- function(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) { .env <- .env %||% parent.frame() .f <- as_invoke_function(.f) future_map2_dfc(.f, .x, purrr::invoke, ..., .env = .env, .progress = .progress, .options = .options) } furrr/R/future_pmap.R0000644000177700017770000000454113275617443015637 0ustar herbrandtherbrandt#' @rdname future_map2 #' @export future_pmap <- function(.l, .f, ..., .progress = FALSE, .options = future_options()) { if (is.data.frame(.l)) { .l <- as.list(.l) } future_pmap_template(purrr::pmap, "list", .l, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map2 #' @export future_pmap_chr <- function(.l, .f, ..., .progress = FALSE, .options = future_options()) { if (is.data.frame(.l)) { .l <- as.list(.l) } future_pmap_template(purrr::pmap_chr, "character", .l, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map2 #' @export future_pmap_dbl <- function(.l, .f, ..., .progress = FALSE, .options = future_options()) { if (is.data.frame(.l)) { .l <- as.list(.l) } future_pmap_template(purrr::pmap_dbl, "double", .l, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map2 #' @export future_pmap_int <- function(.l, .f, ..., .progress = FALSE, .options = future_options()) { if (is.data.frame(.l)) { .l <- as.list(.l) } future_pmap_template(purrr::pmap_int, "integer", .l, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map2 #' @export future_pmap_lgl <- function(.l, .f, ..., .progress = FALSE, .options = future_options()) { if (is.data.frame(.l)) { .l <- as.list(.l) } future_pmap_template(purrr::pmap_lgl, "logical", .l, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map2 #' @export future_pmap_dfr <- function(.l, .f, ..., .id = NULL, .progress = FALSE, .options = future_options()) { # Passing through the template doesn't work because of the way fold() works. # Could parameterize around fold(res, ___), but this is easier if (!rlang::is_installed("dplyr")) { rlang::abort("`future_map_dfr()` requires dplyr") } res <- future_pmap(.l, .f, ..., .progress = .progress, .options = .options) dplyr::bind_rows(res, .id = .id) } #' @rdname future_map2 #' @export future_pmap_dfc <- function(.l, .f, ..., .progress = FALSE, .options = future_options()) { # Passing through the template doesn't work because of the way fold() works. # Could parameterize around fold(res, ___), but this is easier if (!rlang::is_installed("dplyr")) { rlang::abort("`future_map_dfc()` requires dplyr") } res <- future_pmap(.l, .f, ..., .progress = .progress, .options = .options) dplyr::bind_cols(res) } furrr/R/future_options.R0000644000177700017770000001424413274653037016374 0ustar herbrandtherbrandt#' `future` specific options #' #' These options are used by `future()` internally to tweak the environment that #' the expressions are called in. The most important ones are `globals` and #' `packages` which allow you to be explicit about the variables and packages #' that are exported to each worker. #' #' @param globals A logical, a character vector, or a named list for #' controlling how globals are handled. For details, see `Global variables and packages`. #' #' @param packages (optional) a character vector specifying packages #' to be attached in the R environment evaluating the future. #' #' @param seed A logical or an integer (of length one or seven), #' or a list of `length(.x)` with pre-generated random seeds. #' For details, see below section. #' #' @param lazy Specifies whether the futures should be resolved #' lazily or eagerly (default). #' #' @param scheduling Average number of futures ("chunks") per worker. #' If `0.0`, then a single future is used to process all elements #' of `.x`. #' If `1.0` or `TRUE`, then one future per worker is used. #' If `2.0`, then each worker will process two futures #' (if there are enough elements in `.x`). #' If `Inf` or `FALSE`, then one future per element of #' `.x` is used. #' #' @section Global variables and packages: #' #' By default, the `future` package will perform black magic to look up the #' global variables and packages that your `furrr` call requires, and it #' will export these to each worker. However, it is not always perfect, and #' can be refined with the `globals` and `packages` arguments. #' #' `globals` may be used to control how globals #' should be handled similarly how the `globals` argument is used with #' `future()`. Since all function calls use the same set of globals, this function can do #' any gathering of globals upfront (once), which is more efficient than if #' it would be done for each future independently. #' #' * If `TRUE` or `NULL`, then globals are automatically identified and gathered. #' * If a character vector of names is specified, then those globals are gathered. #' * If a named list, then those globals are used as is. #' * In all cases, `.f` and any `...` arguments are automatically #' passed as globals to each future created as they are always needed. #' #' `packages` may be used to control the packages that are exported #' to each worker. #' #' * If a character vector of packages names is specified, those are exported #' to each worker. #' * In all cases, `purrr` is exported, as it is always required on each worker. #' #' @section Reproducible random number generation (RNG): #' #' Unless `seed = FALSE`, this function guarantees to generate #' the exact same sequence of random numbers _given the same initial #' seed / RNG state_ - this regardless of type of futures and scheduling #' ("chunking") strategy. #' #' RNG reproducibility is achieved by pregenerating the random seeds for all #' iterations (over `.x`) by using L'Ecuyer-CMRG RNG streams. In each #' iteration, these seeds are set before calling \code{.f(.x[[ii]], ...)}. #' _Note, for large `length(.x)` this may introduce a large overhead._ #' #' As input (`seed`), a fixed seed (integer) may be given, either #' as a full L'Ecuyer-CMRG RNG seed (vector of 1+6 integers) or as a seed #' generating such a full L'Ecuyer-CMRG seed. #' If `seed = TRUE`, then \code{\link[base:Random]{.Random.seed}} #' is returned if it holds a L'Ecuyer-CMRG RNG seed, otherwise one is created #' randomly. #' If `seed = NA`, a L'Ecuyer-CMRG RNG seed is randomly created. #' If none of the function calls \code{.f(.x[[ii]], ...)} uses random number #' generation, then `seed = FALSE` may be used. #' #' In addition to the above, it is possible to specify a pre-generated #' sequence of RNG seeds as a list such that #' `length(seed) == length(.x)` and where each element is an #' integer seed that can be assigned to \code{\link[base:Random]{.Random.seed}}. #' Use this alternative with caution. #' **Note that `as.list(seq_along(.x))` is _not_ a valid set of such #' `.Random.seed` values.** #' #' In all cases but `seed = FALSE`, the RNG state of the calling #' R processes after this function returns is guaranteed to be #' "forwarded one step" from the RNG state that was before the call and #' in the same way regardless of `seed`, `scheduling` #' and future strategy used. This is done in order to guarantee that an \R #' script calling `future_map()` multiple times should be numerically #' reproducible given the same initial seed. #' #' @export future_options <- function(globals = TRUE, packages = NULL, seed = FALSE, lazy = FALSE, scheduling = 1.0) { .options <- new_future_options( globals = globals, packages = packages, seed = seed, lazy = lazy, scheduling = scheduling ) validate_future_options(.options) } new_future_options <- function(globals, packages, seed, lazy, scheduling) { stopifnot(inherits(globals, c("NULL", "logical", "character", "list"))) stopifnot(inherits(packages, c("NULL", "character"))) stopifnot(inherits(seed, c("logical", "integer", "list"))) stopifnot(inherits(lazy, c("logical"))) stopifnot(inherits(scheduling, c("logical", "numeric"))) structure( list( globals = globals, packages = packages, seed = seed, lazy = lazy, scheduling = scheduling ), class = "future_options" ) } validate_future_options <- function(.options) { # Globals checks if(is.list(.options$globals)) { nms <- names(.options$globals) if(any(nms == "") || is.null(nms)) { stop("Every element of a globals list must be named.", call. = FALSE) } } # Seed checks if(is.integer(.options$seed)) { if( !(length(.options$seed) == 1L || length(.options$seed) == 7L) ) { stop("Integer seeds must be of length 1 or 7.", call. = FALSE) } } # Scheduling checks if(!length(.options$scheduling == 1L)) { stop("scheduling must be length 1.", call. = FALSE) } .options } assert_future_options <- function(.options) { if(!inherits(.options, "future_options")) { stop(".options must be created from future_options().", call. = FALSE) } } furrr/R/future_pmap_template.R0000644000177700017770000002070113276322420017513 0ustar herbrandtherbrandtfuture_pmap_template <- function(.map, .type, .l, .f, ..., .progress, .options) { # Assert future options assert_future_options(.options) # Create function from .f .f <- purrr::as_mapper(.f, ...) # ... required in case you pass .null / .default through for purrr::as_mapper.numeric # Debug debug <- getOption("future.debug", FALSE) # Check .l is list (consistent with purrr) if(!is.list(.l)) { stop("`.x` is not a list (%s)", typeof(.l), call. = FALSE) } # ## Nothing to do? n.l <- length(.l) n_all <- purrr::map_int(.l, length) n.l_elems <- max(n_all) if(any(n_all == 0L)) { return(get_zero_length_type(.type)) } ## Improper lengths possible_len_problems <- which(n_all != n.l_elems) any_possible_problems <- length(possible_len_problems) if(any_possible_problems) { for(prob_id in possible_len_problems) { # Recycle as necessary if(n_all[prob_id] == 1L) { .l[[prob_id]] <- rep(.l[[prob_id]], times = n.l_elems) # Or exit if improper length (with purrr-like error) } else { msg <- sprintf("Element %i has length %i, not %i or %i.", prob_id, n_all[prob_id], 1L, n.l_elems) stop(msg, call. = FALSE) } } } if (debug) mdebug("future_map_*() ...") ## NOTE TO SELF: We'd ideally have a 'future.envir' argument also for ## future_lapply(), cf. future(). However, it's not yet clear to me how ## to do this, because we need to have globalsOf() to search for globals ## from the current environment in order to identify the globals of ## arguments 'FUN' and '...'. /HB 2017-03-10 future.envir <- environment() ## Used once in getGlobalsAndPackages() below envir <- future.envir ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 1. Global variables ## 2. Packages ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .options <- gather_globals_and_packages(.options, .map, .f, .progress, envir, ...) globals <- .options$globals packages <- .options$packages ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 3. Reproducible RNG (for sequential and parallel processing) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - seed <- .options$seed seeds <- NULL # Placeholder needs to be set to null ## Don't use RNGs? (seed = FALSE) if (is.logical(seed) && !is.na(seed) && !seed) { seed <- NULL } # Use RNGs? if (!is.null(seed)) { if (debug) mdebug("Generating random seeds ...") ## future_lapply() should return with the same RNG state regardless of ## future strategy used. This is be done such that RNG kind is preserved ## and the seed is "forwarded" one step from what it was when this ## function was called. The forwarding is done by generating one random ## number. Note that this approach is also independent on length(.x) and ## the diffent FUN() calls. oseed <- next_random_seed() on.exit(set_random_seed(oseed)) seeds <- generate_seed_streams(seed, n_seeds = n.l_elems) if (debug) mdebug("Generating random seeds ... DONE") } ## if (!is.null(seed)) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 4. Load balancing ("chunking") ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - chunks <- generate_balanced_chunks(.options$scheduling, n.l_elems) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 5. Create futures ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## Add argument placeholders globals_extra <- as.FutureGlobals(list(...future.lst_ii = rlang::new_list(n.l, names = names(.l)), ...future.seeds_ii = NULL)) attr(globals_extra, "resolved") <- TRUE attr(globals_extra, "total_size") <- 0 globals <- c(globals, globals_extra) ## At this point a globals should be resolved and we should know their total size ## stopifnot(attr(globals, "resolved"), !is.na(attr(globals, "total_size"))) ## To please R CMD check ...future.map <- ...future.f <- ...future.lst_ii <- ...future.seeds_ii <- temp_file <- NULL nchunks <- length(chunks) fs <- vector("list", length = nchunks) if (debug) mdebug("Number of futures (= number of chunks): %d", nchunks) if (debug) mdebug("Launching %d futures (chunks) ...", nchunks) for (ii in seq_along(chunks)) { chunk <- chunks[[ii]] if (debug) mdebug("Chunk #%d of %d ...", ii, length(chunks)) ## Subsetting outside future is more efficient globals_ii <- globals for(.l_i in seq_along(.l)) { globals_ii[["...future.lst_ii"]][[.l_i]] <- .l[[.l_i]][chunk] } ## Using RNG seeds or not? if (is.null(seeds)) { if (debug) mdebug(" - seeds: ") fs[[ii]] <- future({ # rlang tilde - when serializing with multisession, the pointer becomes 0x0 # Temp solution is to readd base::`~` into the .f environment if necessary ...future.f.env <- environment(...future.f) if(!is.null(...future.f.env$`~`)) { if(is_bad_rlang_tilde(...future.f.env$`~`)) { ...future.f.env$`~` <- base::`~` } } # Attach the dots as a named element, these will be recycled # double list to keep the names once passed to ...future.f ...future.lst_ii$...future.dots <- list(list(...)) # Make persistent file connection if(.progress) { temp_file_con <- file(temp_file, "a") on.exit(close(temp_file_con)) } # The ... of pmap are passed in ...future.dots # The current elements of the list are passed in ... ...future.f_wrapper <- function(..., ...future.dots) { .out <- do.call(...future.f, c(list(...), ...future.dots)) if(.progress) update_progress(temp_file_con) .out } ...future.map(...future.lst_ii, ...future.f_wrapper) }, envir = envir, lazy = .options$lazy, globals = globals_ii, packages = packages) } else { if (debug) mdebug(" - seeds: [%d] ", length(chunk)) globals_ii[["...future.seeds_ii"]] <- seeds[chunk] fs[[ii]] <- future({ # rlang tilde - when serializing with multisession, the pointer becomes 0x0 # Temp solution is to readd base::`~` into the .f environment if necessary ...future.f.env <- environment(...future.f) if(!is.null(...future.f.env$`~`)) { if(is_bad_rlang_tilde(...future.f.env$`~`)) { ...future.f.env$`~` <- base::`~` } } # Attach the current seeds to the lst as a named element, these will be iterated over ...future.lst_ii$...future.seeds_ii <- ...future.seeds_ii # Attach the dots as a named element, these will be recycled # double list to keep the names once passed to ...future.f ...future.lst_ii$...future.dots <- list(list(...)) # Make persistent file connection if(.progress) { temp_file_con <- file(temp_file, "a") on.exit(close(temp_file_con)) } # In the wrapper, refer to the random seeds by name to match them in pmap ...future.f_wrapper_seed <- function(..., ...future.dots, ...future.seeds_ii) { # ...future.seed_ii will be a single element of that object assign(".Random.seed", ...future.seeds_ii, envir = globalenv(), inherits = FALSE) .out <- do.call(...future.f, c(list(...), ...future.dots)) if(.progress) update_progress(temp_file_con) .out } ...future.map(...future.lst_ii, ...future.f_wrapper_seed) }, envir = envir, lazy = .options$lazy, globals = globals_ii, packages = packages) } ## Not needed anymore rm(list = c("chunk", "globals_ii")) if (debug) mdebug("Chunk #%d of %d ... DONE", ii, nchunks) } ## for (ii ...) if (debug) mdebug("Launching %d futures (chunks) ... DONE", nchunks) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 6. Print progress ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(.progress) { poll_progress(fs, globals$temp_file, n.l_elems) } ## FINISHED - Not needed anymore rm(list = c("chunks", "globals", "envir")) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 7. Resolve ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - values <- multi_resolve(fs) # no names with pmap if (debug) mdebug("future_map_*() ... DONE") values } furrr/R/future_map_template.R0000644000177700017770000001576513276077576017374 0ustar herbrandtherbrandt#' @importFrom globals globalsByName cleanup #' @importFrom future future resolve values as.FutureGlobals nbrOfWorkers getGlobalsAndPackages #' @importFrom parallel nextRNGStream nextRNGSubStream splitIndices #' @importFrom utils capture.output str future_map_template <- function(.map, .type, .x, .f, ..., .progress, .options) { # Assert future options assert_future_options(.options) # Create function from .f .f <- purrr::as_mapper(.f, ...) # ... required in case you pass .null / .default through for purrr::as_mapper.numeric # Setup debug <- getOption("future.debug", FALSE) ## Nothing to do? n.x <- length(.x) if (n.x == 0) { return(get_zero_length_type(.type)) } if (debug) mdebug("future_map_*() ...") ## NOTE TO SELF: We'd ideally have a 'future.envir' argument also for ## future_lapply(), cf. future(). However, it's not yet clear to me how ## to do this, because we need to have globalsOf() to search for globals ## from the current environment in order to identify the globals of ## arguments 'FUN' and '...'. /HB 2017-03-10 future.envir <- environment() ## Used once in getGlobalsAndPackages() below envir <- future.envir ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 1. Global variables ## 2. Packages ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .options <- gather_globals_and_packages(.options, .map, .f, .progress, envir, ...) globals <- .options$globals packages <- .options$packages ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 3. Reproducible RNG (for sequential and parallel processing) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - seed <- .options$seed seeds <- NULL # Placeholder needs to be set to null ## Don't use RNGs? (seed = FALSE) if (is.logical(seed) && !is.na(seed) && !seed) { seed <- NULL } # Use RNGs? if (!is.null(seed)) { if (debug) mdebug("Generating random seeds ...") ## future_lapply() should return with the same RNG state regardless of ## future strategy used. This is be done such that RNG kind is preserved ## and the seed is "forwarded" one step from what it was when this ## function was called. The forwarding is done by generating one random ## number. Note that this approach is also independent on length(.x) and ## the diffent FUN() calls. oseed <- next_random_seed() on.exit(set_random_seed(oseed)) seeds <- generate_seed_streams(seed, n_seeds = n.x) if (debug) mdebug("Generating random seeds ... DONE") } ## if (!is.null(seed)) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 4. Load balancing ("chunking") ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - chunks <- generate_balanced_chunks(.options$scheduling, n.x) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 5. Create futures ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## Add argument placeholders globals_extra <- as.FutureGlobals(list(...future.x_ii = NULL, ...future.seeds_ii = NULL)) attr(globals_extra, "resolved") <- TRUE attr(globals_extra, "total_size") <- 0 globals <- c(globals, globals_extra) ## At this point a globals should be resolved and we should know their total size ## stopifnot(attr(globals, "resolved"), !is.na(attr(globals, "total_size"))) ## To please R CMD check ...future.map <- ...future.f <- ...future.x_ii <- ...future.seeds_ii <- temp_file <- NULL nchunks <- length(chunks) fs <- vector("list", length = nchunks) if (debug) mdebug("Number of futures (= number of chunks): %d", nchunks) if (debug) mdebug("Launching %d futures (chunks) ...", nchunks) for (ii in seq_along(chunks)) { chunk <- chunks[[ii]] if (debug) mdebug("Chunk #%d of %d ...", ii, length(chunks)) ## Subsetting outside future is more efficient globals_ii <- globals globals_ii[["...future.x_ii"]] <- .x[chunk] ## stopifnot(attr(globals_ii, "resolved")) ## Using RNG seeds or not? if (is.null(seeds)) { if (debug) mdebug(" - seeds: ") fs[[ii]] <- future({ # rlang tilde - when serializing with multisession, the pointer becomes 0x0 # Temp solution is to readd base::`~` into the .f environment if necessary ...future.f.env <- environment(...future.f) if(!is.null(...future.f.env$`~`)) { if(is_bad_rlang_tilde(...future.f.env$`~`)) { ...future.f.env$`~` <- base::`~` } } # Make persistent file connection if(.progress) { temp_file_con <- file(temp_file, "a") on.exit(close(temp_file_con)) } ...future.map(seq_along(...future.x_ii), .f = function(jj) { ...future.x_jj <- ...future.x_ii[[jj]] .out <- ...future.f(...future.x_jj, ...) if(.progress) update_progress(temp_file_con) .out }) }, envir = envir, lazy = .options$lazy, globals = globals_ii, packages = packages) } else { if (debug) mdebug(" - seeds: [%d] ", length(chunk)) globals_ii[["...future.seeds_ii"]] <- seeds[chunk] fs[[ii]] <- future({ # rlang tilde - when serializing with multisession, the pointer becomes 0x0 # Temp solution is to readd base::`~` into the .f environment if necessary ...future.f.env <- environment(...future.f) if(!is.null(...future.f.env$`~`)) { if(is_bad_rlang_tilde(...future.f.env$`~`)) { ...future.f.env$`~` <- base::`~` } } # Make persistent file connection if(.progress) { temp_file_con <- file(temp_file, "a") on.exit(close(temp_file_con)) } ...future.map(seq_along(...future.x_ii), .f = function(jj) { ...future.x_jj <- ...future.x_ii[[jj]] assign(".Random.seed", ...future.seeds_ii[[jj]], envir = globalenv(), inherits = FALSE) .out <- ...future.f(...future.x_jj, ...) if(.progress) update_progress(temp_file_con) .out }) }, envir = envir, lazy = .options$lazy, globals = globals_ii, packages = packages) } ## Not needed anymore rm(list = c("chunk", "globals_ii")) if (debug) mdebug("Chunk #%d of %d ... DONE", ii, nchunks) } ## for (ii ...) if (debug) mdebug("Launching %d futures (chunks) ... DONE", nchunks) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 6. Print progress ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(.progress) { poll_progress(fs, globals$temp_file, n.x) } ## FINISHED - Not needed anymore rm(list = c("chunks", "globals", "envir")) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 7. Resolve ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - values <- multi_resolve(fs, names(.x)) if (debug) mdebug("future_map_*() ... DONE") values } furrr/R/future_map2_template.R0000644000177700017770000001660213276077567017445 0ustar herbrandtherbrandtfuture_map2_template <- function(.map, .type, .x, .y, .f, ..., .progress, .options) { # Assert future options assert_future_options(.options) # Create function from .f .f <- purrr::as_mapper(.f, ...) # ... required in case you pass .null / .default through for purrr::as_mapper.numeric # Debug debug <- getOption("future.debug", FALSE) ## Nothing to do? n.x <- length(.x) n.y <- length(.y) if (n.x == 0 || n.y == 0) { return(get_zero_length_type(.type)) } ## Improper lengths if (n.x != n.y && !(n.x == 1 || n.y == 1)) { msg <- sprintf("`.x` (%i) and `.y` (%i) are different lengths", n.x, n.y) stop(msg, call. = FALSE) } ## Recycle .x or .y to correct length if needed # At this point, the only allowed extension is if .x or .y is length 1 if(n.x > n.y) .y <- rep(.y, times = n.x) if(n.y > n.x) .x <- rep(.x, times = n.y) if (debug) mdebug("future_map_*() ...") ## NOTE TO SELF: We'd ideally have a 'future.envir' argument also for ## future_lapply(), cf. future(). However, it's not yet clear to me how ## to do this, because we need to have globalsOf() to search for globals ## from the current environment in order to identify the globals of ## arguments 'FUN' and '...'. /HB 2017-03-10 future.envir <- environment() ## Used once in getGlobalsAndPackages() below envir <- future.envir ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 1. Global variables ## 2. Packages ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .options <- gather_globals_and_packages(.options, .map, .f, .progress, envir, ...) globals <- .options$globals packages <- .options$packages ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 3. Reproducible RNG (for sequential and parallel processing) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - seed <- .options$seed seeds <- NULL # Placeholder needs to be set to null ## Don't use RNGs? (seed = FALSE) if (is.logical(seed) && !is.na(seed) && !seed) { seed <- NULL } # Use RNGs? if (!is.null(seed)) { if (debug) mdebug("Generating random seeds ...") ## future_lapply() should return with the same RNG state regardless of ## future strategy used. This is be done such that RNG kind is preserved ## and the seed is "forwarded" one step from what it was when this ## function was called. The forwarding is done by generating one random ## number. Note that this approach is also independent on length(.x) and ## the diffent FUN() calls. oseed <- next_random_seed() on.exit(set_random_seed(oseed)) seeds <- generate_seed_streams(seed, n_seeds = n.x) if (debug) mdebug("Generating random seeds ... DONE") } ## if (!is.null(seed)) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 4. Load balancing ("chunking") ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - chunks <- generate_balanced_chunks(.options$scheduling, n.x) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 5. Create futures ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## Add argument placeholders globals_extra <- as.FutureGlobals(list(...future.x_ii = NULL, ...future.y_ii = NULL, ...future.seeds_ii = NULL)) attr(globals_extra, "resolved") <- TRUE attr(globals_extra, "total_size") <- 0 globals <- c(globals, globals_extra) ## At this point a globals should be resolved and we should know their total size ## stopifnot(attr(globals, "resolved"), !is.na(attr(globals, "total_size"))) ## To please R CMD check ...future.map <- ...future.f <- ...future.x_ii <- ...future.y_ii <- ...future.seeds_ii <- temp_file <- NULL nchunks <- length(chunks) fs <- vector("list", length = nchunks) if (debug) mdebug("Number of futures (= number of chunks): %d", nchunks) if (debug) mdebug("Launching %d futures (chunks) ...", nchunks) for (ii in seq_along(chunks)) { chunk <- chunks[[ii]] if (debug) mdebug("Chunk #%d of %d ...", ii, length(chunks)) ## Subsetting outside future is more efficient globals_ii <- globals globals_ii[["...future.x_ii"]] <- .x[chunk] globals_ii[["...future.y_ii"]] <- .y[chunk] ## stopifnot(attr(globals_ii, "resolved")) ## Using RNG seeds or not? if (is.null(seeds)) { if (debug) mdebug(" - seeds: ") fs[[ii]] <- future({ # rlang tilde - when serializing with multisession, the pointer becomes 0x0 # Temp solution is to readd base::`~` into the .f environment if necessary ...future.f.env <- environment(...future.f) if(!is.null(...future.f.env$`~`)) { if(is_bad_rlang_tilde(...future.f.env$`~`)) { ...future.f.env$`~` <- base::`~` } } # Make persistent file connection if(.progress) { temp_file_con <- file(temp_file, "a") on.exit(close(temp_file_con)) } ...future.map(seq_along(...future.x_ii), .f = function(jj) { ...future.x_jj <- ...future.x_ii[[jj]] ...future.y_jj <- ...future.y_ii[[jj]] .out <- ...future.f(...future.x_jj, ...future.y_jj, ...) if(.progress) update_progress(temp_file_con) .out }) }, envir = envir, lazy = .options$lazy, globals = globals_ii, packages = packages) } else { if (debug) mdebug(" - seeds: [%d] ", length(chunk)) globals_ii[["...future.seeds_ii"]] <- seeds[chunk] fs[[ii]] <- future({ # rlang tilde - when serializing with multisession, the pointer becomes 0x0 # Temp solution is to readd base::`~` into the .f environment if necessary ...future.f.env <- environment(...future.f) if(!is.null(...future.f.env$`~`)) { if(is_bad_rlang_tilde(...future.f.env$`~`)) { ...future.f.env$`~` <- base::`~` } } # Make persistent file connection if(.progress) { temp_file_con <- file(temp_file, "a") on.exit(close(temp_file_con)) } ...future.map(seq_along(...future.x_ii), .f = function(jj) { ...future.x_jj <- ...future.x_ii[[jj]] ...future.y_jj <- ...future.y_ii[[jj]] assign(".Random.seed", ...future.seeds_ii[[jj]], envir = globalenv(), inherits = FALSE) .out <- ...future.f(...future.x_jj, ...future.y_jj, ...) if(.progress) update_progress(temp_file_con) .out }) }, envir = envir, lazy = .options$lazy, globals = globals_ii, packages = packages) } ## Not needed anymore rm(list = c("chunk", "globals_ii")) if (debug) mdebug("Chunk #%d of %d ... DONE", ii, nchunks) } ## for (ii ...) if (debug) mdebug("Launching %d futures (chunks) ... DONE", nchunks) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 6. Print progress ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if(.progress) { poll_progress(fs, globals$temp_file, n.x) } ## FINISHED - Not needed anymore rm(list = c("chunks", "globals", "envir")) ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ## 7. Resolve ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - values <- multi_resolve(fs, names(.x)) if (debug) mdebug("future_map_*() ... DONE") values } furrr/R/future_modify.R0000644000177700017770000000376313276355501016171 0ustar herbrandtherbrandt#' Modify elements selectively via futures #' #' These functions work exactly the same as [purrr::modify()] functions, but allow #' you to modify in parallel. #' #' @inheritParams purrr::modify #' @inheritParams future_map #' #' @details #' #' From `purrr`) Since the transformation can alter the structure of the input; #' it's your responsibility to ensure that the transformation produces a valid #' output. For example, if you're modifying a data frame, `.f` must preserve the #' length of the input. #' #' @return #' An object the same class as .x #' #' @examples #' #' library(furrr) #' library(dplyr) # for the pipe #' #' \donttest{ #' plan(multiprocess) #' } #' #' # Convert each col to character, in parallel #' future_modify(mtcars, as.character) #' #' iris %>% #' future_modify_if(is.factor, as.character) %>% #' str() #' #' mtcars %>% future_modify_at(c(1, 4, 5), as.character) %>% str() #' #' @export future_modify <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { UseMethod("future_modify") } #' @export future_modify.default <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { .x[] <- future_map(.x, .f, ..., .progress = .progress, .options = .options) .x } #' @rdname future_modify #' @export future_modify_at <- function(.x, .at, .f, ..., .progress = FALSE, .options = future_options()) { UseMethod("future_modify_at") } #' @export future_modify_at.default <- function(.x, .at, .f, ..., .progress = FALSE, .options = future_options()) { sel <- inv_which(.x, .at) .x[sel] <- future_map(.x[sel], .f, ..., .progress = .progress, .options = .options) .x } #' @rdname future_modify #' @export future_modify_if <- function(.x, .p, .f, ..., .progress = FALSE, .options = future_options()) { UseMethod("future_modify_if") } #' @export future_modify_if.default <- function(.x, .p, .f, ..., .progress = FALSE, .options = future_options()) { sel <- probe(.x, .p) .x[sel] <- future_map(.x[sel], .f, ..., .progress = .progress, .options = .options) .x } furrr/R/future_map2.R0000644000177700017770000000723513276355467015552 0ustar herbrandtherbrandt#' Map over multiple inputs simultaneously via futures #' #' These functions work exactly the same as [purrr::map2()] functions, but allow #' you to run the map in parallel. Note that "parallel" as described in `purrr` #' is just saying that you are working with multiple inputs, and parallel in #' this case means that you can work on multiple inputs AND process #' them all in parallel as well. #' #' @inheritParams purrr::map2 #' @inheritParams future_map #' #' @return #' An atomic vector, list, or data frame, depending on the suffix. #' Atomic vectors and lists will be named if `.x` or the first element of `.l` is named. #' #' If all input is length 0, the output will be length 0. #' If any input is length 1, it will be recycled to the length of the longest. #' #' #' @examples #' #' library(furrr) #' \donttest{ #' plan(multiprocess) #' } #' #' x <- list(1, 10, 100) #' y <- list(1, 2, 3) #' z <- list(5, 50, 500) #' #' future_map2(x, y, ~ .x + .y) #' #' # Split into pieces, fit model to each piece, then predict #' by_cyl <- split(mtcars, mtcars$cyl) #' mods <- future_map(by_cyl, ~ lm(mpg ~ wt, data = .)) #' future_map2(mods, by_cyl, predict) #' #' future_pmap(list(x, y, z), sum) #' #' # Matching arguments by position #' future_pmap(list(x, y, z), function(a, b ,c) a / (b + c)) #' #' # Vectorizing a function over multiple arguments #' df <- data.frame( #' x = c("apple", "banana", "cherry"), #' pattern = c("p", "n", "h"), #' replacement = c("x", "f", "q"), #' stringsAsFactors = FALSE #' ) #' future_pmap(df, gsub) #' future_pmap_chr(df, gsub) #' #' @export future_map2 <- function(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) { future_map2_template(purrr::map, "list", .x, .y, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map2 #' @export future_map2_chr <- function(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) { future_map2_template(purrr::map_chr, "character", .x, .y, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map2 #' @export future_map2_dbl <- function(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) { future_map2_template(purrr::map_dbl, "double", .x, .y, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map2 #' @export future_map2_int <- function(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) { future_map2_template(purrr::map_int, "integer", .x, .y, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map2 #' @export future_map2_lgl <- function(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) { future_map2_template(purrr::map_lgl, "logical", .x, .y, .f, ..., .progress = .progress, .options = .options) } #' @rdname future_map2 #' @export future_map2_dfr <- function(.x, .y, .f, ..., .id = NULL, .progress = FALSE, .options = future_options()) { # Passing through the template doesn't work because of the way fold() works. # Could parameterize around fold(res, ___), but this is easier if (!rlang::is_installed("dplyr")) { rlang::abort("`future_map2_dfr()` requires dplyr") } res <- future_map2(.x, .y, .f, ..., .progress = .progress, .options = .options) dplyr::bind_rows(res, .id = .id) } #' @rdname future_map2 #' @export future_map2_dfc <- function(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) { # Passing through the template doesn't work because of the way fold() works. # Could parameterize around fold(res, ___), but this is easier if (!rlang::is_installed("dplyr")) { rlang::abort("`future_map2_dfc()` requires dplyr") } res <- future_map2(.x, .y, .f, ..., .progress = .progress, .options = .options) dplyr::bind_cols(res) } furrr/R/load-balancer.R0000644000177700017770000000144113274650566015772 0ustar herbrandtherbrandtgenerate_balanced_chunks <- function(scheduling, n.x) { debug <- getOption("future.debug", FALSE) if (is.logical(scheduling)) { if (scheduling) { nbr_of_futures <- nbrOfWorkers() if (nbr_of_futures > n.x) nbr_of_futures <- n.x } else { nbr_of_futures <- n.x } } else { ## Treat 'scheduling' as the number of futures per worker. stopifnot(scheduling >= 0) nbr_of_workers <- nbrOfWorkers() if (nbr_of_workers > n.x) nbr_of_workers <- n.x nbr_of_futures <- scheduling * nbr_of_workers if (nbr_of_futures < 1) { nbr_of_futures <- 1L } else if (nbr_of_futures > n.x) { nbr_of_futures <- n.x } } chunks <- splitIndices(n.x, ncl = nbr_of_futures) if (debug) mdebug("Number of chunks: %d", length(chunks)) chunks } furrr/R/future_imap.R0000644000177700017770000000364113276355402015623 0ustar herbrandtherbrandt#' Apply a function to each element of a vector, and its index via futures #' #' These functions work exactly the same as [purrr::imap()] functions, but allow #' you to map in parallel. #' #' @inheritParams purrr::imap #' @inheritParams future_map #' #' @return #' A vector the same length as .x. #' #' @examples #' #' library(furrr) #' \donttest{ #' plan(multiprocess) #' } #' #' future_imap_chr(sample(10), ~ paste0(.y, ": ", .x)) #' #' @export future_imap <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map2(.x, vec_index(.x), .f, ..., .progress = .progress, .options = .options) } #' @rdname future_imap #' @export future_imap_chr <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map2_chr(.x, vec_index(.x), .f, ..., .progress = .progress, .options = .options) } #' @rdname future_imap #' @export future_imap_dbl <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map2_dbl(.x, vec_index(.x), .f, ..., .progress = .progress, .options = .options) } #' @rdname future_imap #' @export future_imap_int <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map2_int(.x, vec_index(.x), .f, ..., .progress = .progress, .options = .options) } #' @rdname future_imap #' @export future_imap_lgl <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map2_lgl(.x, vec_index(.x), .f, ..., .progress = .progress, .options = .options) } #' @rdname future_imap #' @export future_imap_dfr <- function(.x, .f, ..., .id = NULL, .progress = FALSE, .options = future_options()) { future_map2_dfr(.x, vec_index(.x), .f, ..., .id = .id, .progress = .progress, .options = .options) } #' @rdname future_imap #' @export future_imap_dfc <- function(.x, .f, ..., .progress = FALSE, .options = future_options()) { future_map2_dfc(.x, vec_index(.x), .f, ..., .progress = .progress, .options = .options) } furrr/README.md0000644000177700017770000002237513276336226014246 0ustar herbrandtherbrandt [![Travis build status](https://travis-ci.org/DavisVaughan/furrr.svg?branch=master)](https://travis-ci.org/DavisVaughan/furrr) [![CRAN status](https://www.r-pkg.org/badges/version/furrr)](https://cran.r-project.org/package=furrr) # furrr The goal of furrr is to simplify the combination of `purrr`’s family of mapping functions and `future`’s parallel processing capabilities. A new set of `future_map_*()` functions have been defined, and can be used as (hopefully) drop in replacements for the corresponding `map_*()` function. The code draws *heavily* from the implementations of `purrr` and `future.apply` and this package would not be possible without either of them. ## What has been implemented? The full range of `map()`, `map2()`, `pmap()`, `imap()`, `modify()`, and `invoke_map()` functions have been implemented. This includes strict versions like `map_dbl()` through `future_map_dbl()` and predicate versions like `map_at()` through `future_map_at()`. ## Installation You can install the released version of furrr from [CRAN](https://CRAN.R-project.org) with: ``` r install.packages("furrr") ``` And the development version from [GitHub](https://github.com/) with: ``` r # install.packages("devtools") devtools::install_github("DavisVaughan/furrr") ``` ## Example `furrr` has been designed to function identically to `purrr`, so that you can immediately have familiarity with it. ``` r library(furrr) library(purrr) map(c("hello", "world"), ~.x) #> [[1]] #> [1] "hello" #> #> [[2]] #> [1] "world" future_map(c("hello", "world"), ~.x) #> [[1]] #> [1] "hello" #> #> [[2]] #> [1] "world" ``` The default backend for `future` is a sequential one. This means that the code will run out of the box, but it will *not* be in parallel. The design of `future` makes this incredibly easy to change so that your code does run in parallel. ``` r # You set a "plan" for how the code should run. The easiest is `multiprocess` # On Mac this picks plan(multicore) and on Windows this picks plan(multisession) plan(multiprocess) # This DOES run in parallel! future_map(c("hello", "world"), ~.x) #> [[1]] #> [1] "hello" #> #> [[2]] #> [1] "world" ``` If you are still skeptical, here is some proof that we are running in parallel. ``` r library(tictoc) # This should take 6 seconds in total running sequentially plan(sequential) tic() nothingness <- future_map(c(2, 2, 2), ~Sys.sleep(.x)) toc() #> 6.08 sec elapsed ``` ``` r # This should take ~2 seconds running in parallel, with a little overhead plan(multiprocess) tic() nothingness <- future_map(c(2, 2, 2), ~Sys.sleep(.x)) toc() #> 2.212 sec elapsed ``` ## Progress bars Who doesn’t love progress bars? For `multiprocess`, `multicore`, and `multisession` plans, you can activate a progress bar for your long running task with `.progress = TRUE`. Note that these are still a bit experimental so feedback is welcome. You should get a nice progress bar that looks like this: ## A more compelling use case This example comes from a Vignette from `rsample`. The vignette performs a 10 fold cross validation with 10 repeats of a GLM on the attrition data set. If you want all the details with explanation, see [the vignette](https://topepo.github.io/rsample/articles/Working_with_rsets.html). The vignette example runs pretty quickly on its own, so to make things more…interesting we are going to use 20 fold CV with 100 repeats. ``` r library(rsample) data("attrition") names(attrition) #> [1] "Age" "Attrition" #> [3] "BusinessTravel" "DailyRate" #> [5] "Department" "DistanceFromHome" #> [7] "Education" "EducationField" #> [9] "EnvironmentSatisfaction" "Gender" #> [11] "HourlyRate" "JobInvolvement" #> [13] "JobLevel" "JobRole" #> [15] "JobSatisfaction" "MaritalStatus" #> [17] "MonthlyIncome" "MonthlyRate" #> [19] "NumCompaniesWorked" "OverTime" #> [21] "PercentSalaryHike" "PerformanceRating" #> [23] "RelationshipSatisfaction" "StockOptionLevel" #> [25] "TotalWorkingYears" "TrainingTimesLastYear" #> [27] "WorkLifeBalance" "YearsAtCompany" #> [29] "YearsInCurrentRole" "YearsSinceLastPromotion" #> [31] "YearsWithCurrManager" ``` Set up an rsample split tibble of 20 fold CV with 100 repeats. ``` r set.seed(4622) rs_obj <- vfold_cv(attrition, v = 20, repeats = 100) rs_obj #> # 20-fold cross-validation repeated 100 times #> # A tibble: 2,000 x 3 #> splits id id2 #> #> 1 Repeat001 Fold01 #> 2 Repeat001 Fold02 #> 3 Repeat001 Fold03 #> 4 Repeat001 Fold04 #> 5 Repeat001 Fold05 #> 6 Repeat001 Fold06 #> 7 Repeat001 Fold07 #> 8 Repeat001 Fold08 #> 9 Repeat001 Fold09 #> 10 Repeat001 Fold10 #> # ... with 1,990 more rows ``` The model formula below is going to be used in the GLM. ``` r mod_form <- as.formula(Attrition ~ JobSatisfaction + Gender + MonthlyIncome) ``` For each split, we want to calculate assessments on the holdout data, so a function was created to allow us to apply the model and easily extract what we need from each split. ``` r library(broom) ## splits will be the `rsplit` object with the 90/10 partition holdout_results <- function(splits, ...) { # Fit the model to the 90% mod <- glm(..., data = analysis(splits), family = binomial) # Save the 10% holdout <- assessment(splits) # `augment` will save the predictions with the holdout data set res <- broom::augment(mod, newdata = holdout) # Class predictions on the assessment set from class probs lvls <- levels(holdout$Attrition) predictions <- factor(ifelse(res$.fitted > 0, lvls[2], lvls[1]), levels = lvls) # Calculate whether the prediction was correct res$correct <- predictions == holdout$Attrition # Return the assessment data set with the additional columns res } ``` Finally, `purrr` was used to map over all of the splits, apply the model to each, and extract the results. First in sequential order… ``` r library(purrr) library(tictoc) tic() rs_obj$results <- map(rs_obj$splits, holdout_results, mod_form) toc() #> 30.095 sec elapsed ``` Then in parallel… ``` r library(furrr) plan(multiprocess) tic() rs_obj$results <- future_map(rs_obj$splits, holdout_results, mod_form) toc() #> 14.211 sec elapsed ``` We don’t get a 4x improvement on my 4 core Mac, but we do get a nice 2x speed up without doing any hard work. The reason we don’t get a 4x improvement is likely because of time spent transfering data to each R process, so this penalty will be minimized with longer running tasks and you might see better performance (for example, 100 fold CV with 100 repeats gave `122` seconds sequentially and `48` seconds in parallel). The implementation of `future_lapply()` does include a scheduling feature, which carried over nicely into `furrr` and efficiently breaks up the list of splits into 4 equal subsets. Each is passed to 1 core of my machine. ## A few notes on performance ### Data transfer It’s important to remember that data has to be passed back and forth between the cores. This means that whatever performance gain you might have gotten from your parallelization can be crushed by moving large amounts of data around. For example, if instead of returning a results data frame in the above example, we returned the larger `glm` model object for each split, our performance drops a bit. ``` r model_only <- function(splits, ...) { # Fit the model to the 90% mod <- glm(..., data = analysis(splits), family = binomial) mod } plan(multiprocess) tic() rs_obj$results2 <- future_map(rs_obj$splits, model_only, mod_form) toc() #> 20.807 sec elapsed ``` Luckily, the `glm` model is relatively small, so we don’t experience much loss, but there are model objects out there that can be 10’s of MBs in size. For models like those, I would advise wrapping up the work you want each core to do into a function, and only returning the actual performance metric you are looking for. This might mean a little bit more work on your side, but it results in smaller objects, and faster performance. This performance drop can especially be prominent if using `future_pmap()` to iterate over rows and return large objects at each iteration. ### Progress bars Progress bars are best used when iterating over relatively few long running tasks. For instance, they are great when training over hyperparameters of a deep learning model, but I would not suggest them when iterating over the rows of a 100k row data frame. I’ve used every trick that I know to make them have minimal performance impact, but you will see degredation when using them with *lots* of elements to iterate over. ## What has not been implemented (yet)? - `walk()` - This will likely not be implemented as it is used for side effects which would not be seen on the parallel workers. - `lmap()` ## Found a bug? Feel free to open an issue, and I will do my best to work through it with you\! furrr/MD50000644000177700017770000000360013277053215013260 0ustar herbrandtherbrandt4dc8cd1893fde435564947ed3e2d3c66 *DESCRIPTION 937e9853b292358d323adfbdfeaf0179 *NAMESPACE a3b932a0cb4abcf6d62975f22ea781c4 *NEWS.md 2021f5925a1ea2102e554b94d6a423b6 *R/fold.R cf670546101f4ef58d308a7a5fdd015d *R/future_imap.R 101a5397b7c9353d618ae7a2e021fce8 *R/future_invoke_map.R 5b5e9920c7057cf54f984408b5193fd3 *R/future_map.R 9651f35cb132457d4e3305adc43233e0 *R/future_map2.R 3cace3c575c185e37394b6a0b925316a *R/future_map2_template.R 344d837b700623fbb073881a96395df0 *R/future_map_template.R acff2c3371eae10929259979fd3753a2 *R/future_modify.R 847052b42cc885db789dd60899e0b9cc *R/future_options.R da8b92276591b1cec9b709fce7db6da7 *R/future_pmap.R 26a85cb0944965e5d7d40f5170a0f27d *R/future_pmap_template.R 552e94dab6c838fe41d9fb79747c87d3 *R/global-gatherer.R 8d086e977df8980dd8d2c38954e40faa *R/load-balancer.R b469a839884ff63adee2b1bb21fe8597 *R/progress.R 27aba8fca7a2f6302fc04d366c7e2f6c *R/random-seeds.R 3af368b22af944846927fcb7b450508c *R/resolve.R e4251e3173ebdaf56a6aaef78c0ce298 *R/utils.R 56d9b49e1f6104ab3ef2f6f85bd7801b *README.md f861a74b048ea917ea7815848ab6639b *man/figures/progress.gif 1dd54d4f2bc62059cc0a982e53327434 *man/fold.Rd 1ff8b2118e7019bbae4207b9e78d7ba5 *man/future_imap.Rd 3d3aacd0ab8d7fcb43288bdde98d8a70 *man/future_invoke_map.Rd 82bbe9653dc75cbd88a9a4d5a9d83a9d *man/future_map.Rd 1c0103aebb1861d7ab84f6aa052de793 *man/future_map2.Rd 816a0c0a684616ce8ef56f9b2ae44253 *man/future_modify.Rd ee6886000d61d47a936b78d74aac4a76 *man/future_options.Rd 975564c058a476dbdbc9e79e7ffd1257 *tests/testthat.R 3fcce05607b3cba9d972af2222ba2d4c *tests/testthat/helper-test-setup.R 2429c27e4c5c27f0b02a93fcb8cbace8 *tests/testthat/test-future-options.R 236eba4be3bbc85d7e8c5211080afb4d *tests/testthat/test-map.R b345cc03074d35c59337fc7ed6fb0e12 *tests/testthat/test-map2.R 766c36e689d595f281e306f1494c42c4 *tests/testthat/test-pmap.R 8dd25d1eb773655861fc7626b59ee441 *tests/testthat/test-progress.R furrr/DESCRIPTION0000644000177700017770000000203313277053215014455 0ustar herbrandtherbrandtPackage: furrr Version: 0.1.0 Title: Apply Mapping Functions in Parallel using Futures Depends: R (>= 3.2.0), future (>= 1.6.2) Imports: globals (>= 0.10.3), rlang (>= 0.2.0), purrr (>= 0.2.4) Suggests: listenv (>= 0.6.0), dplyr (>= 0.7.4), testthat Authors@R: c( person("Davis", "Vaughan", email = "dvaughan@business-science.io", role = c("aut", "cre")), person("Matt", "Dancho", email = "mdancho@business-science.io", role = c("aut")) ) Maintainer: Davis Vaughan Description: Implementations of the family of map() functions from 'purrr' that can be resolved using any 'future'-supported backend, e.g. parallel on the local machine or distributed on a compute cluster. License: LGPL (>= 2.1) LazyLoad: TRUE URL: https://github.com/DavisVaughan/furrr BugReports: https://github.com/DavisVaughan/furrr/issues RoxygenNote: 6.0.1 NeedsCompilation: no Packaged: 2018-05-14 19:53:18 UTC; davisvaughan Author: Davis Vaughan [aut, cre], Matt Dancho [aut] Repository: CRAN Date/Publication: 2018-05-16 16:04:29 UTC furrr/man/0000755000177700017770000000000013274650566013535 5ustar herbrandtherbrandtfurrr/man/figures/0000755000177700017770000000000013265401155015165 5ustar herbrandtherbrandtfurrr/man/figures/progress.gif0000644000177700017770000031727013265401155017532 0ustar herbrandtherbrandtGIF89aX$.#.."2$0!.+8?[{%/ * )!%AQ]GQ[O[%! =7?P[KV^6K=Q!BM9^Bi<4TGf7Hh %+4DOgZ!E@Q^9JXHnB2Nl[pt+?P\BT`0E[z 9IV1GUTr9 "SwHm,[}F;Va+ ZgrM[gGf<=`@S_FdMkf9EPHWD&D[OY`,66AJ)/!+2#-4#.6(28)15+6<-9A.8@.JOFGI%BZ&07&185B?inuVdht]LP:K[ynNb*rdpy'D[OlJ0;)'$7Fcgn(3440$.4^@M>N"( u&("45)6)7l3A9HQ^,<;Ntqw%/^i=baW'3H%/6%07! NETSCAPE2.0!Optimized using ezgif.com! ,X *\ȰÇ#JHŋ3jȱǏ CIrdɠŲ˗0clɈT8sɳϟ[,uٶ/@#09ևc8t 3 ?V óĢ0Kx#v+/2ȽKMZyI뱜@s͛3H1#Lh0@ 4󚋺#8"/K7<@-@Ke/q>i[@$`SLϜҭ0pb.1&R4F-ˊS՚#-2/c>B4[7G.9F"}hy"iZ.2- 2 C31l.on 1|*9 N120.N Cr֜8wigw`#jGRyaJ%&ʛ ZrL*0Wp68,hy<`, o:?q<8oTHU(L WB^0!.̇+}У8p=kCdm X$kcŁX̢.z{"L.TACCcy<ך6I.n#H#Qw܏> Y4"#) ɉ\‘P ־Ȓb: (GIIBM~3h@:PԠM(BІ.ԡD'PZeY8ΎziHEJґ%M)JUҖ,)Le:SԦ8ͩNA5; PJԢHMRԦR 4ԪZXͪVoխz өpC|' :65P*su&־] `K QK 8Ɋ`lf]7U-5a;k_HQefdb@\fM 0XvM?Y:Ѝtc*V=挋9EZF+ɞD!,+]So ī ! byCu+7ыV|%>D@FVGw$'L`b~0VrkȝgL;7ֽOԞMp veXgy,%F!Mkq>@yLauX1"_ pV6 ` 6A 2;g^1(/Pq` f0/<T8,n6k#wyŔ*{M0ыӠw*?EO1g)X0g29l|Y(T@1`WP2>5Yшg@[1 -#Jh̜f/|-Av,UCR^1~1Ym#mՈw1/|fزw%E #AfŴeXp9(ǀٿdZZhZc 7v\IEt_Ua\v]b"7Yoc0FJszY>+ebtb#~rV㞭G89~V9Qyڵ=q|'bOjr~Kry؊l#ؐ6oV mՀsrg}wCXuW]'~j}y7NZ`w${)xXc؂84XJq:Cl~2=r%CHFXGHLMPRSHUhWZ\^AB~2P[uhȂjl؆npr8tXvxqz{ȇ}8xH!(58PHȉ艠8xX؊Ȋ(Xhx؋Qe!CʸЌΘPȌ8x @ (蘎x؎Hhx؏)H`ڈ^ЍhD` )Θ :`ɍ"PI*#ɒ-029/I5i86i9c`= 4@ i!P@9@`FI@ؔ`Ki:9dYfyhjiɓ蓝]PP8YJP \Z`_4& TI}P PYI/@B zY,`_YSp7ɖYyYnf7i[ EiOǩ p mp~VFٚɑCD [40)Y>Y\:ڛutGIЗi~YY(p/)e`zɘّ(TY4,P^00/v)zٞKXI!9DZFzHJLڤNPڒ!v;PN񉝄iB𢒠e4BK DiC@9^:ОiY.06 :Zz8+ ߹x@͹JI)S{~z Րָ8:PzY pI(bBZzؚڪSʔzgAʟJcʠ `S z4٭W <ʩz +ٰ˛=T;۱ˤ{mP Ǫ骮C *2k` 0 h *۲/+,KM:2I0˴Tk3 pV\;!Ȩ p'boF $*`(I Pɀ~˱@@kk 03sZd,{c۷IiHȷM)) ȷ3Q nj/) `   &( _z9,З-Ip7 irj {۞-Z E{izWK0VD 5`/kiAڌD%@`ְl@HK_"-j>yi ̣)ZE[,p)yC , ېˍZ{0mPSp{/:܌j\Z0 iY^ ( pӊȊȌȎ<_Yyˍy `C9u S yyc©9ğyYzĬ| l9 /ꢜ peH鹙@9<éj{rP 옜̢=9>=kIM/&b|I,ݮg\Xݞɿ$.P˙G|p8g j- ԑ(D] ހ,`0`Qk--/z+9Ө֠P;SɌfsO@\3PB 5PDkDuQH4RJ+'sSN;SPCuTRuDLSUuUVOӴUXcWgV\ UI^{W`vXb5WdUcuYhol6Zjiv[nk˶[pF=\t[uuYvߕwY^|w_~_.{5`K .uava#8%b]Ec;>bCydK6dc[recJgkygKQ0桞DYXVE)N餙D669TڤV{mCemb?vTݞP=&pw 't)%I|sC}5n,1w=G<T:7uܩzw5r7x7Qқwy裗݁~{^D|ß|#>}]}}?~~?ݿ$`X@ d`=FPDxFY,9A?y#DIx0+ Y-\0aWCʅ9vHA QCxD$&Q_1b&>Qʆ [7E,)YE0a$#mXF4idnF8qcXGd%-yILډ&IPqe)MybXT*JW:e-mye/-X0}yLd&t$& JhFSEc3j^Sb4Mp8yNt3T$' < XO{>?~hZSBPD);^wUgG=pTHWҬT+eiK]R1 7W4i;Q RT驆WS~y@]MyTѨDPgSFU\*SԩfU[VjUf` XJVxz\ezVUrYi֯U{k_WV%laZW+u elcXFVle-{YfVlg="֧0MjiM{v%jϊZVmmmtLmo}[W%nq{\&Wens\FWӥnu{]Unw]W%oy{^*Neo{^Wo}{_Wo_X&pۛ[=@ fp`GXp-lG#p=aX#&qM|bX+fq]bX3qm,b7;q}c YC&r|dW1ajd(GYSr|e,gY[re0Yc&sd4/Yiff8YpFp|gEK@̤JET'["ÏJ DbS wɆ@ 2ar Ib))F$G{)#pf%&!H FmDbeبDb>hTb{A8$Y`9gz[>E +j.z V[CDẗE,2F\@@@g,LE Q(1zH9Y85~w)7ʰy $?1΅SтkB8CL(BA ^؅ $@x@wõn6JN_?@A y-˝$ji._?iKs 9>Ll!eĝA%"%4X2Az"$xۂ; 2Nlu@B8DXFxHJL؄NPR8TXVxXZ\؅^`b8dXfxhjl؆U=n#\<EF3x(#! ,9%H*\ȰÇ#JHŋ3jȱǏ C(S\ɲ˗0cʜI$͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈJ̸c@8l3kJd `!ͨS?$&)AתsͻNȣ*B'N=& 7y* I~Nި#'tQ{{Og/=pa;C]44w 6 o@1F}@є #t*'e d4DTEI"LcБXm+ɇ>XǒH6K.vTFcXf\#`)dihlp)tix! ,5p"*\ȰÇ#JHŋ3jȱǏ g<(ɓ(S\ɲˍ1 I͛8s!#Ο@ JHE*]ʴΞ$JJՐWjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sR" 4PP2e1xIXPC;ν{AyO" !Ͼ`&9f])R?pc;\_ZP`BԆJ<(l(!F&mعi̅dDi~ -q0[:Ȑz1 :! ,Up*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞6ڸsͻVA>| ȓ+_μ#E8Buسdĝ{D22=ax+^@~ ҟ>|ݧzAj &&S(~1%TP 0(ކG"/`O@gAa7("It &PpP=x( +l cf40ƐD%2r;ou1Ǎ^0IQXfuyj`F`DIX A`8@$w~q5,BvLaj~_A" @BH &$؁o\  {!,P i4@ }PE6@ DER뿌+q0lrh!P"*}րHYƳ3j HB@k#PF cJ%l%TX):pЉovJB2;b0_h[H& ϑkR¯.ihE'kEǸ-xavQ#X)#Fp %@ !t ]ĊabD|:et{z2 Yu%G!:?g|1/d5" /<8/&AAoؘ$P+ r BAtL(z̈́FPL |x!\ gH@8̡w8G?Hyk_ ӷDJmh.Z}3l"4 r>JPR4%~m?@ AX p<W(6q\|B=8A@큐%@?`VhEH jЯ"v&6q&ITrLǔY=9H$iRzkX'KA 8JJG V ^,[S~B^' "lThdfEJZI!uP況a;@ =!j"DPvbౘRT./:RDQm]R(#brl 4W qR̴r)+Q80Rz? I*"#-DJTa~AS%Veã Ђ,&i..#nk@Wg,)4*'KD8v ]֐M!` XdJS:W-5A4]+V@SIҬВb@`6iPBl܇; Urk+a 2" lI4Af\T (AP(> jt@ʔa"~%!$Ʋ|Aq%! Q7\Wv_A W%DMLʽ4Ы*¦"!0U!|R.HQ@l͘NQ4;p%[T9?NM=UC|-8 & n KLQ א8Tu>!q5l{neKs x2P8, ,*07oDUS +MT7Ssx[lXXpVݢiHL43! g.?U`iF\}Bz#uxCDYj]$͉6BV%'W \@R0Rfj-,޲!л,-?a!{#c#BM"W7aCJ]@Bj`E-n}ld",oLRJ-A\^3mRM}LFA\@)(UsDj I+ \l:iF1 >:00dٓ^_!k%E VPr~(r@Նm8"<}!xwePm7\;$1*k<[()t@V!S2|m@ i,5@7aCz\I"$B;f$)7`0'*2e$DSf6Eq;Cuogf ]UȏNԏi!+X Zk4Ek%Q6 2U0iZ4UW"i_ %c`6`P7 YB"5=%58I V%Yy ٔ!KHyW Q92=P#GEZyȕHVPgd{c=vEVYRf h?1YKW_ZuE6iO-(!p[bIJ}G4"8$6$ RD"d:2?PQf8D1SWIٙ&󛴙09ř)-B[-:Iٙ\ ݔ/A טNd>)@|(T$t㩛ChG ?A 8q &$AIFYCʙ4*o4ztT:&/u$E O$Dz$;4ڠ⹞5Z!z:7@?:zt;?  jR:VLJ-j`Q`b:dZczh:ﱥ]&Tp@i:jڤIDzYʦQ}zm*z*ڨ1x*j:JHѧOz!:Zzڪ:Zzګ:ZzȚʺڬ:Zzؚںڭ:Zz蚮꺮ڮ:Zzگ;[{ ۰;[{۱ ";$[&{(*,۲.02;4[6{8:<۳>@B;D[F{HJL۴NPR;T[V{XZ\۵^`b;d[f{hjl۶npr;t[v{xz|۷~;[{۸;[{3! -,S@hA*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k 87ФS^ͺ롄M{ڸsͻ8_$?wtћkΰ.r0 #` 8&$DXfCYGH%FH"a,dE@B>BAڀHAVLxAAb0 ؑ(_]dB*b%(A9H*C Et|H!y 3f9Xn` FBv@1dž!!FA#(Aq$6g|FA=`B%'A\pDiС{>vtФ !I"A}y BSx"i*%G$xc"l$j[EXiB]ET;m%|x˂kuC|.nW> $T{b'+'I0%\LvawF;J#*TA8;%xL]U17G,k2+\H!2Ϗ0}A7-DWDXI1m9sdɾ3N+QPtI#1nR +%vЩ.`$ `Y Ds.QqQa7fݵA=4T-:C?~Ɇ B5nx!gwm hJ_]9D 5_/vQЎ#, AAg5Dz6R(dp* R  >Z ‡%iW;A#(Da5(0ju,XU! EDeB zHZڠD8z2#! qAo!j'`!XHl4ھQ!H+6:"Z\ 2d'"'l%,=C0`$MQi8C>,] S\-!b"3h$ *A(PzP4(CPi(m'X Xİ= 4&|dHO(HLv'T}c(ӕ:UpuiЗδDKs63

H dFE>n+[9]֮~흭 RB؎$sYtOyxDM b+Ф-V0MuQ\%-捼8&;MÖ,{xm"!7x)2.xi+#/8BvY~8xYf[Ь$Qk#ᯕE$rz0h:NCtۤC #VJ?Zlv2J:=h9<ԨVSViaՒ!Pw3^8UMbNf;FKj[fn{MrNvMzη~NO;'N[ϸ7{ GN(OW0gN8Ϲw@ЇNHOҗ;PԧN[XϺַ{`NhOpNxϻOO;񐏼'O[ϼ7{GOқOWֻgOϽwOOo)0/N! ,; H*\ȰÇ#JHŋ3jȱǏ C(S\ɲ˗0cʜISɚ8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝK]\p߿ Lˆ+@Ő#K T,M̹3J ๴^ͺļ]˞}ڸsB N&_͘A34`+ ZEh3{LBx?d<H2w메$]D@N$Bj0Tcf!ff^%zy)gBW3~tJ|]qbg܏S \@! ,l3 p€*\ȰÇ#JHŋ3jȱG3Hɓ(S\ɲe\ʜI͛8I3ϟ@ j1$ H*]TgϦPJtիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^M( e2iE #kҀ0!{+H z @, ,:a 9y /' -[&TWAD@Vhfv!S:aJ ‰‘A%R҂L=߈pIbhTmAG[#DkR`Jo HJ>FuM! ,Upd*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞6ڸsͻVA>| ȓ+_μУKNسVw_s7`ȫ>@:(.MDd~D('ss(dB$R}(T_E0M FC\$V aq+.b%<@@)DiHAL6@} d@i\:p@Ag< T\9QrHjni bi ' @,gjy؍R'uD6㡌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'N,> ! ,S@(@*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k 87ФS^ͺ롄M{ڸsͻ Nȓ+_μ9b0#A8~ PDw8 ȫw`˟OϿȚh!B!^`m,Szl5 @D"G"pGDHP%Rav<}A6]G4T E- dED@T1E-\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXgZD@! 4,WiHJȰÇ#JHŋ3jȱǏ C R*qBɲ˗0cʜI͛8hԶmdJѣH*]$FP}%իXjʕi63|i`.l ʶ۷pBlUѩMaoKܿ #AhxkLt) ӨS>L-yrV۸sF΃lȓ+_$hlp*=#WU!1  4g_*/Ͽ?( aa5ȁ 5GeG_B6f(sELJi($(z%0(4h8xT:Q"F H&)т"Ht4lGBX>51RG.C$WO 4Vl)ӖO<" אK⧛R"uvh6ra^,SLFA(K@tCk>jꅆZ$HR y7ZyuQh J8l0l` @zF)T`HyGTw>"''^b-Fo*Kޛ,Y'0 anj F{ijmAnjkг 3lp03l37ZT$C;rB2[JT3x4V|Lr^ b]giͶot=v3?wǒzl'7C.ӎkѓg+/~9ԃ㕣Hy)NxG:N;rCqA2DT0|/AD/M߼GOwO=Qϧ(roOo @OH~]`r?!<` /`$ ZP'x'DGB :I "ІDa +ar i8@6%p&RP,GEp4@ / cA6  4#HD!6&*"m, + Q(XD!؆\`x d5HA(\@dP *1<@N@ i,SAupRa&'֐0X 4ġH`nz_h@9 )fA*Qh&A(A?~Oь +C3R@Ps"sP"޹F L);  ( 4QCMR)R0 H2Ag4AFɎ +P āmV/Q`LOeUYךֲ@e 0[&)ˬptՁ`A@? "^6zMP,e5 N$cA"/ #f[pmm?]Yz%Ecnk~-)\$0h:7rMjW?ڲ<i<#4{!cK:xHXz5 ":p/mtu[."+[@nv ]fuGk\Ye_$UZk?QB{քDGnAR/zql!_}D͊V7)7cfpvCޕP.0,yX)VY,)kpv8B(`P< 3D :q C *5A#:ķk#eՋTcݯ@NJ.;| ]@@Z"_4y] UpQ\R3 `N9JF(L3$@{%=O36w0X0gP {0T@-ۍ1(Ql^!bByQ<]ዱ64Aqh4Ā `@1}: +$䩹CA X6B-0 99!!!ِh$IDZ](YO{"FT#'"m93o/U@^Ae$8:^i*QCK!_UDLmB7VD=?1 lhϋ! ן R)m qn-KDR /t}J>4'PPF`8Jpy=|̉h<k(!l,7CؠtX+_; 7l{DC)E $~pN!dHǵ{֡Ea0E(U 7q2vl^u SQyQQ4o$@| Kwa.pH4x`@ReHCcN\7fw 63qGT`=<hpanր>hc Q>Ȅ{P qoj1&z^E_e=SFF ueM) OugsWJdJnr!GCw Q75=QV(h=%omsGoJaUi6tYvXuz熣@U6kq9(HT`PXF5 `Vdg|6k8@ uD6kB Ϸqr HK;V]'vhNv&-u4hsF5RuxVhFd&X uT0 ~G@5E XwFւ%UevJev~V*Ï|ev^?|W2Y|6k# va_mg 8k6$a|œ|gJ_`({/$Ƌɤedv@ؑ\Y '.{w[/dFx[? FeaO Xh5 3 @Տts"Eg3t]c1ns[s8[[&Iy^VV[qZƖmIZT4m%F{[YXA8\h\嘠ItV\śh)Ĝ6y C%xu9YydyV Y&`aI艎H^)h4xlYy`X _oDT Y@"ՠse&EwK15zڡ*A:$!j%*'*+0*-Zg1Z63B77Sģ@>ZAZ:NfJڡH}KP:TZVzXZ\ڥ^`b:dZfzhjlڦnpr:tZvzxz|ڧ~:ZzڨZzک:ZzW {ڪ :TzLګ:ZzȚʺڬ:Zzؚںڭ:Zz蚮꺮ڮ:Zzگ;[{ ۰;[{۱ ";$[&{(*,۲.02;4[6M{8:<۳>@B;D[F{HJL۴NPR;T[V{XZ\$3Xb{$^[?! B,WHJȰÇ#JHŋ3jȱǏ C rF UBɲ˗0cʜI͛MBh鈙Zp JѣH*HZ իXjݺԔ`\9p3r]˶۷piaҸgܿ ,,BrrjG\#KLG|$$@--I=̲ӨS%P3g3nUP۸s& Hމw+_μyH ?iPױ<#YU弻!-w^fZ٫ ev8a {iU=-gVhTÆt (Qa#,0(4֘چ6']FD9щhF 雑PFI^ 0"d0ٕUf Ji2vM ? h<@I&J|k9"*h~&hsd^|MF>EK@̤JET'["ÏJ DbBS w&D 2.r`DR|c(Ali෠hBӶ\ AsyꙞE.En֎T0F0z3"TPq{PAB2ET,`WDDQ@&|2-4M @P v\s 1Ql4PP=#l.3OwآP] A{QY@$@ ` Dg'EЉ4m&x!!DCDP tIB !`TᬇZGP"M qO@MD>حGߪNaBa@xL;&%H>ٵ!uBBV $A^H"`!O LD}LT伦25cD!V'2l2@ JX©00jfP*TZ s5rn>"IgCa|!Z\EFlY81Q,ohEmkQ(P+cDkL$IØE $:  ;$4$h3QC˰9"tx#L)JneW; 5IM$ g|WIVrbVC҇Q{#%.OD?lEXaʼ DBbpY.?ey,_X@oCh8!)B:]r kpz8;)uJD.B"i yͳ Aȥxd0'a|ONŭE,! k{9%OL(.'fZH'&Dv f`TB$Zl;BOt,E֐59iCIeȍkмXF QC^(VM+BS0펒<&V+7EdE%󺡆#Y;*xL&`>a[Z]&XIل?'-6pcN&m@mJ$J:D=ֽ>wDMUG/;񐏼'O[ϼ7{GOқOWֻgOϽwOO;ЏO[Ͼ{)OO*$t8~v ؀8Xx؁ "8$X&x(*,؂.0284X6x8:<؃>@B8DXFxHJL؄NPR8TXVxXZ\؅^`b8dXfxhjl؆npr8tXvxxz|؇~8Xx؈=8Xx؉8Xx,-#\;4(#! , G&H*\ȰÇ#JHŋ3jȱǏ 9N1c!S\ɲ˗0cʜIE8sɳϟ@ J4΢H*]ʴӧH}@QXjʵׂ9c<lկhӪ]˶mưbuKݻxN%k֬T𑷰È7[ +Lea=kϠC7W΢S^͚eפ۸s?mtoɺ ē+_؇sA1N:Gν8pNӫ_Ͼ($a]oT @*Fh !!~!! &) Qql8_0XD08DG @z (CL6PF)TViXfeml%FpF_6@hO&xIDgHWk%!k(H yGh `biy~ 1 J,UiTEG6TxY *z 'E+xkBI>D4! @,C3p*\ȰÇ#JHŋ3jȱǏ CI \ɲ˗0cʜI͛ c@ɳϟ@ J:*]ʴӧPZD CիXj"R]ÊKُ;Ϊ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3#l䣳͠C tHNͺ덪M{7ݟ^;!AwN{+_~6͞n̳kǴ[2ӫ7g~')(\;s<0 JGq5aJ c}vhXj(Tx4:5j8P<#(DiH&L6PF)TViXfY Z)/R@lp)|9xX=d@}ɒ9vkf^4e,d`s$ @xpCYA av~Z6A&: e/T vB ` @l"),|uN&vҒ9-فA! ,Up4*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹}8FШS&29r޹{G s1;x wTAӼ+_urGwlM~--#:,74xᩲ5lܠhuGqg'5 3)$Vh[!CR ]s$Ev:&a%(cV2މy3#S"$?iT$d$G6PF)TViXf\v`)dih&CP@p)tiT ܩ瞈^ Z$e@袎Y0j@BVC!Q" p p:م),Q '(U!`RC¯, ĺ +S.qĮP#  E&WEЎT-72bKA:[mb9H@Ŀlp G,Xg  [q Clq<, ,fQoZ,DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H Z̠7z GH0cN8!bf! ,S@@*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸O%YѢ˘3k̹ϠCo$ѨS%5t@&MۭsK#^ȓ/E| }XVNzF)νËOӫ_Ͼ˟OϿ `A `w.FVhaZ\!{n]QtTT!~ AX( [zM!RP,3@Ȥrl:ШtJZجvzQ znd ~}{c0e[; H/L`#:3jȱ- =IrU#D!.r0!" Rt$J@ n!R2$4 ֭| b 'xɆNسkνËOӫ_Ͼ˟OڇP(+)@@ ~gB` Af cHXd `C=Y!Fe0!$`c|_np# DA/TŒC aL~h K홠d^!e|])dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`D؃! ,S@@*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k 87ФS^ͺ롄M{ڸss w#Cm!ȓ+_μУKNسkνËO18/ϿZHxW$`U^Ge,JF^SdO Ԅ0aEJ"` ,AcF\2HX>yRiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰ƿ*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-кJAt[N! 4,WiHJȰÇ#JHŋ3jȱǏ C R*qBɲ˗0cʜI͛8h6dJѣH*]$>ZʴիXj4xE06hq]˶۷p!Ԧi4߿ J 4QeKLeNw8E@NRuuӨKg+x+۸s#T^6 G1ȓ+i3ܥJUy,νw Bz|TW{`+š,bο`5a@\{:|ƥTF(a`Qȅdބv!Vah≂-,];0#;h8S\]@S ;X>H6N\N)TG4yHBuMyUBPBЈyM 4 1Վ柀CC9y隕 (/eAo)yPqvheAAކAQ}R$5_25(DZ$H_RfҚ:$O94 QG. ⌳IBrnC}5P+`>y9) `dʌeD3|Dcmh`AZDX fY|" Cu[ʟ)Kr͋ 6x([*xKx-:_:ӊP=s F a %馗-);jߍpkλֺͯrb}oK}{}g~݃?=ӏO>_*v*ٯ4;cN@IT` r ު&́%Q(B^$l@ɝ0*ty/k!=!P2!YgċI1D`@QF$83:i\G5Ҡblx;ʱ(niH0#h!BF"AŊ A q$( x(ӨGr_<%*8!+SJZe)wIYB2/YX}fS$f2)MeDf1?i̋ f0FXҜ޼FL@n&FN ,9 ~&LH'Ԁ $D@s  "@ЄCze>9"8HBNvzb H&$p(HwS|!'`)9+ŕT(C0 )JS P I5 Ӯz!!I(8@LiR@A(պz5Dp Ad t)`aJT}5]'-yJ$ ",#+9[zlUJBrLi[}j2lFmC@((\,[ĵLm6s-"9^!#.EbV1I`#mv'^.L6ӷz͹peKp &t₁oSO࠰Z>!rIֺYߎ]ppS`ᵚȌ'&KGTmO vgY*:teLq< $$t`rmJ{48C'3r)! 9&0[;>qH4r a!hh9v@06ܶ!=ECZ4S~6V _֢@!8 &tFmyYntx&CkM/5Q'aV7;mFLzz=XyCLɁ" -αix~ f&lXVa +S9A~7n'()@ w(F(Bʀlu Hy|5(*k'? ޭ<&)b}$E4pϝC>aGD!m|{H u2-ASTv~"$b8Ԅ4GfDaBa@DtyP7&%$϶Ai0$r! "x!! "P 7ypNKʄ=L8(V 0CĶ5A$.I [ƦFtgc>vDFme4쌋XnȲM"SajF lF188*i#JD1g AHǘ% " (&Q!3؆PINzc $gHpr-@F5{(0mg.Za8̲(ap1/x2]/ y}X25M(͐ =<9,=+zce[yY2DqDE ña }-^Pj$xZz"lD2`wF-!WĖXa*T +³:4 QH% ą)0$COXiݍ=JXbf-jn k5/մnwN CιBp*xR! – JCVKA!Qj"Ԇ3™U]G3 șu&3$jTեh\ t ,2MXS$ 謶oCQiO3aʚd~qc ly*EE݀Ej%e v!TfȔ$BS#h6D90CLP[l-V^g{Ֆ۱e|Xߓ Zۂm(&7 ,ڗDg}Vsp(qd&]vqkѬ|!I7rp6PYg'X^2JHșU@C 8Rݎ;Rm\ xFNE@q 9<9t"DEz];OiH8~B8+[9dvH# }y[H7{GOқOWֻgOϽwOO;ЏO[Ͼ{OOϿ8XxX. ׀g ($Ktw؁ "8$X&x(*,؂.0284X6x8:<؃>@B8DXFxHJL؄NPR8TXVxXZ\؅^`b8dXfxhjl؆npr8tXvxxz|؇~8Xx؈U8Xx؉8Xx؊8Xxtx,#N;5(#! , #^"LM$v`eo% *(Ǚ$HrGsڒr)!#p#qщD tu}P !*\ȰÇ#JHŋ3jȱǏ C f$ 2ɲP0c4!"0j3` 2I%1kvҩPLiW?/+2tBɒse6@ %M#c"&Bi1дEO&άDV ! @,R3@4-@31ŋ;2Ҝ0ݗɪ; H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣHX#ӧ~JիXjʵׯ`ÊKٳhӪ]˶-VLnP+ Ls+^̸ h옖p„`hEl$Q n&"š GĐ%e%pHnw6#@edjMD Q D ,(_xL^94=@! ,Up4*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_\iHͣKNسkνËOӫ_Ͼ}!(O_T' s& h`jk`00vE6\[RaOhTB6uGJ,1K JU`HA.< $ ŁFKc- XdyP7eGWrc&#h69暈ΙW杅 ':Щ&'rcDLzv ZpfItY(:f*m:bJz}jek&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|9߀.n'7G.Wngw砇.褗! ,S@@*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞Mjͻ Nȓ+_μУKN`(O^ӫ_ O7F!y*1!a50_O-`ݧ "5aLX] NPEb_C0I0≁0G@$C"C]Xp0ޏH9tdP*qM@Xf\v`)L5!he)p)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7HIu[N! ,)F CâgdҞDafH`e|bkG^+cih]jl-뒦|D  ^2C(H S5D!! G,RMp*\ȰÇ#JHŋ3jȱǏ CI \ɲ˗0cʜI͛c@ɳϟ@ J2t]ʴӧPJԫXjʵŤ*Kٳ+w]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ NX#BNسkνËOӫ_Ͼ=vL~p/(h} 6UҤAY0Ab-pD@ ؐ %"Y!p& F% aSȐG 0G@ ՠAL $d)ٙ fF^4ĒYYqF{u)h~}IT(Hh2%B f:顉*2e|E~ԍzA Xk많 kij,DN:X* B@Ʋ.QhQJ@,;F0{Тb:or:Ke YDdR !Kx.{dYT ,la1(" Р1 /2 @h,I؜:[E+DH'L7ŒjJ5)G qBhq ě*z @@H'_XfY嗁ecD U晀iR"h XKmEAiK1T&FY睄~$ ?Y$ YS u萔BjF^ YO^ĥz_jꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n'MN,N ! ,S H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI󟃛jɳϟ@ JѣH*]ʴӧPJJի ֯+(KVزhӪ] ۷p K'׺xE!u@*y&8BE  l#'Jʠ'/JB(B~C|^rPڸsͻ N ȓ+_μУKNسkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&䍒L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무D ! W,VHJȰÇ#JHŋ3jȱǏ CQ*qBɲ˗0cʜI͛8hԶ+dJѣH*]$>ZʴիXjݺ4ەxE06hq]˶۷p!Ԧi4߿ J WQeKLNw^9u@NRuuӨ/g+x+۸sT^6 G1ȓ+i3ܥJUy,ν Bz|TW{`+š,bο`5a@\{:|ƥTF(!`QȅdބvVah(,0Xڅ2hcN@Ïi#(.QXsHVieG egT5i$RyhnET]$ZpyZX(AQ}R$5_25(&M4ⅥdGf[(U䆉fj$tLyPrTdBQ G鲦8lQl_ M-\X $=F4G (U a y?<ѭYh Іڥ^!jAzۚcJT&܊k/%x;UޱVO&0fp1R# ILg<qpqDyWpEC()$<7DF7_3FĜD=Gm&XDwFKu< 5@D@-\ qA5ADH0׀9@B% ]'Y&QBB `}d~ ]Am]@L_ܕA{\`  Aq۪Wt`]1jDu\BPԓ1E')N~W%Lr5)p&TF??fXfp􋠑 (o4KA͂A )i"XD,p~`j@B&< %R ̠GHu%=D (ĆumsFBҰK6(l-erŗ\!`L-oHG!0#FPL(fR8;1[@֖p?Pmkh "ty74 aϏl48>b*;y=];  H<2rB:R QA*9rx6 3U+Hy=Ja D5h P_Mg@q'y/I2L@8DS !HAt @:ˉ "+`@WFsV9 CAfMC/kHt9 64D r 9Vz#CP+@q&̩"X(!5kȶ 5?!ԤV!`=aU<.!R:QkOIڇ^ki&q<7,\G1H&ېV 0,Ck؃x4;E-؈bM@Hy'S °Ғulrr 6]B OX؁WfRj6jX_65:_ P0!#5CG4l"{&,i 1\ qHl 6bW$`HHg 7ė@WMȉչ^;)F?K`mB)BK>Q\́@Un'C2ON~$ɫԭjMEu.eς8gǃ >a_2`#WRsL&q_FFBdv񼭩;a5EtPLOC$-%GS$G@OG֮-mdqeemkTq5BV6{0N 0?oq$}>yoF#{X1AhZHHV )եݍiؾlnsm-{c)A wX?_~ K!޷}X~_0˾=ܿۂ¾DB+r|~=hu[P}718W ^QND0B0|z wuU8WCTXd*g-Q 2ya o!cB?Y@[wO3DŽLxo'vNIv]w5x&F37j 'wK5K=EOĂ'IsYI5 PPVQu-461C6@qBlvFd^mI7!n[uRSNx=É扠X # /{xh⊝膸؋pw8H$H،>h8.H#(؍)RG8Xx蘎긎؎8Xx8u׏9Yy ِ9 GDܘБ v$)v%y(*,ْ.0294Y6y8:<ٓ>@B9DYFyHJLٔNPR9TYVyXZ\ٕ^`b9dYfyhjlٖnpr9tYvyxz|ٗ~9Yy٘9Yyٙ9Yyٚ9Yyٛ9Yyș)NU2,@HA! A,VHJȰÇ#JHŋ3jȱǏ CqF UBɲ˗0cʜI͛MBhYZp JѣH*HZ իXjݪT`\9p3r]˶۷piaҸgܿ ,,ArrjG\#KLrG|$@--I=̲ӨS[%P3g3nUP۸s EHމw+_μ9H ?iPױ<#YU弻-w^fZ٫ ev(a {iU=-gVhTÆt (Qa#,0(4hچ6cd;)HE"oD&Hv  `$V5XrD@[2$A^F(NYV/t#9@@O!15P/3'^42M&xvHah5j$p )IFlXF@ bX~ke(*F$Gb$, T-rԔV ^Ⱥa.jEzI֖oǤQ=#ߊ)^]t& :Sc"TKfW`d33"TPq{PAA2E(uQP D)l = 2mC @P v 1QdD7Q\ER\ ]QPw9Ԅ4GfDaBa@DtyP7&%$϶Ai0$r! "x!! "P 7ypNKʄ=L8(V 0CĶ5A$.I [ƦFtgc>vDFme4쌋XnȲM"SajF lF188*i#JD1g AHǘ% " (&Q!3؆PINzc $gHpr-@F5{(0mg.Za8̲(ap1/x2]A9nsB*̐*WI,GmrL z:y$|?:Zu-Cvc*ذ&4'O_z,.),VJ`eԾ$:#蚛ϾLv"^ 7LKf\r.y=Prp6PY;\l 'h^2` [GYU@C 8Rݎ;Rmf54fխ~Mn>#g ;.If4⧯DEZ];nȵ_96f @B8DXFxHJL؄NPR8TXUyVZ\؅^`b8dXfxhjlmo'kh txL凇gHw|8Xx؈8Xx؉8Xx؊8Xx؋8XxȘʸ،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9YyّYA"9$;0%#!5! , #'H@/" 21ȰÇ#JHŋ3jȱǏ CI&IiƤ˗0cʜI͛8s,`,n H*]ʴӧP9(D ʁr0'ׯ`ÊK)Nq&,KݻxF@@ ҩ#È+^d Ĥ0˘3k̹ϠCMӨS^ͺװc˞Mpڸs_LPݘNM K$L AҲ%L)fP#޽- -Ͼ',[ Ma]a}.iT`}_LhaUf{Ef !}Q$h(,0(4h0pQqG@GُDtE&d]^`рKWTAD! C,R<C4-C31ŋ;2Ҝ0ݗɪ; H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣHX#ӧ~JիXjʵׯ`ÊKٳhӪ]˶-VL nP+ Ls+^̸ h옖p„ `hEl$Q!n&"š GĐ%e%pHnw6#Ce0djMD Q D ,(_xL^94߽Ͽ !6AJD fO! ,Up4*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_\iHͣKNسkνËOӫ_Ͼ}!(O_T' s& h`jk`00vE6\[RaWe0!$`ac|pBJ,1K JU`HA.8# ( KcQE,TEY|NViXf\v`)fkYH䘅s` a44t|'o ŸbD9S@*R&h@:DUJĥj?y M2Ɜ:S*.j*C*p&;UªKVpǮv{.覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|D߀.n'7G.Wngw砇.褗n騧ꬷ6C@! ,S@@*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞Mjͻ Nȓ+_μУKN^|:<ӫJd`{q<`J%pAHXb S h5łHABM`VT BIA} F$H$X2Rب<@9Ux IG&$ dS+F)TViY]^\zbfg#ayU!tiwHWia|I*蠄j|j*6裐F iWh"d^v駠*ꨤjLA꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|߀.n@J[N! ,7)C҇gdDafH`ebkG 4\Ah|u auISBl3> C *h%"G%I͛8sɳϟ@ JѣH*]ʴ,.NJyԫXy.ׯZʴիXjݺ4xE06hq]˶۷p!Ԧi4߿ J @QeKLNw9@NRuuӨ/ g+x+۸sT^6 G1ȓ+i3ܥJUy,ν Bz|TW{`+š,bο`5a@\{:|ƥTF(!`QȅdބvVah(,0Xڅ2hcN@Ïi#(.QXsHVieG egT5i$RyhnET]$ZpyZX(AQ}R$5_25(&M4ⅥdGf[(U䆉fj$tLyPrTdBQ G鲦8lQl_ M-\X $=F4G (U a y?<ѭYh Іڥ^!jAzۚcJT&܊k/%x;UޱVO&0fp1R# ILg<qpqDy@pEC()$<7DF7_3FĜD=Gm&XDwFKu< 5@D@- qA5ADH0׀9@B% ]'Y&QBB `}d~ ]Am]@LܕA{\`  Aq۪@t`]1jDu\BPԓ1E')N~W%Lr5)p&TF??fXfp􋠑 (o4KA͂A )i"XD,p~`j@B&< %R ̠GHu%=D (ĆumsFBҰK6(l-erŗ\!`L-oHG!0#FPL(fR8;1[@֖?Pmkh "ty74 aϏl48>b*;y=];  H<2rB:R QA*9rx6 3U+Hy=Ja D5h P_Mg@q'y/I2L@8DS !HAt @:ˉ "K `@@FsV9 CAfMC/kHt9 64D r 9Vz#CPL @q&̩"X(!5kȶ 5?!ԤV!`=aU<.!R:QkOIڇki&q<7,\G1LSdr8 `h-{n]FyC, H)b!} BVZMNdz A;C۬XJ͆2T@bCgk &duH_Md$2M`/w I_C`; O1{ W$`HHg 7ė@WMȉչ^;)F?K`mB)BK>Q\́@Un'C2OyN~$ɫ̭jMES#eςص|hGJVs'c41|%,M&Ҝ|##!2xS $VSB15 O LxesFσ ״lem;"cfg1amcr Y iЎav{޺Kڼ7C9Җbժq́d-@֐]]I++N"*.o :#*ANqZ&\mP/"ou7 r#ԇNSb)Es+DЭNEx brV3̊fE{qia4w)#'n; $^1޻Jq;N3~5z$ 3mĊ Bd}Z8@R $=z _}z= "8D ],QHCcX-8@ҳ}(j.齰:}웟;Ԃh!kIJ HfxWVȈnW+rڷEDy gD88sXw؁⒁8;x}$ T$*+8~0Xq‚67؂Vă@؃9HAXV!HFA3HL12(V(#TW\`b8dXfxhjl؆npr8tXvxxzXƷ~8Xx؈x߰x, "8Dx؊8Xx؋8XxȘʸ،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9Yyّ "9$Y&y(*,ْ.0294Y6y8/:<ٓ>@B9DYFyHJY%ҔNI~P#L9@! A,VHJȰÇ#JHŋ3jȱǏ CqF UBɲ˗0cʜI͛MBhYZp JѣH*HZ իXjݪT`\9p3r]˶۷piaҸgܿ ,,ArrjG\#KLrG|$@--I=̲ӨS[%P3g3nUP۸s EHމw+_μ9H ?iPױ<#YU弻-w^fZ٫ ev(a {iU=-gVhTÆt (Qa#,0(4hچ6cd;)HE"oD&Hv  `$V5XrD@[2$A^F(NYV/t#9@@O!15P/3'^42M&xvHah5j$p )IFlXF@ bX~ke(*F$Gb$, T-rԔV ^Ⱥa.jEzI֖oǤQ=#ߊ)^]t& :Sc"TKfW`d33"TPq{PAA2E(uQP D)l = 2mC @P v 1QdD7Q\ER\ ]QPw9Ԅ4GfDaBa@DtyP7&%$϶Ai0$r! "x!! "P 7ypNKʄ=L8(V 0CĶ5A$.I [ƦFtgc>vDFme4쌋XnȲM"SajF lF188*i#JD1g AHǘ% " (&Q!3؆PINzc $gHpr-@F5{(0mg.Za8̲(ap1/x2]A9nsB*̐*WI,GmrL z:y$|?:Zu-Cvc*ذ&4'O_z,.),VJ`eԾ$:#蚛ϾLv"^ 7LKf\r.y=Prp6PY;\l 'h^2` [GYU@C 8Rݎ;Rmf54fխ~Mn>#g ;.If4⧯DEZ];nȵ_96f @B8DXFxHJL؄NPR؄7VxXZ\؅^`b8dXfxh @nvot(tuXt|؇~8Xx؈8Xx؉8Xx؊8Xx؋8XxȘʸ،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9YyRx,$NC :&Y#! , #' ^"LM$v `eo% *(М$HrGsr)!#p#qډD ѩ#*\T Ģ0ŋ3jȱǏ CIɓ(S\ɲ˗0cʜI`8s^L0JѣL G$L AҪ!%L%f0^XϞ=- +˖A'o,W MAp]am.i*e_P÷ 3iu[/,3Yf(|MӨS^ͺװc˞M۸s⢻o<N, _6D! E,R=E4-E31ŋ;2Ҝ0ݗɪ; H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣHX#ӧ~JիXjʵׯ`ÊKٳhӪ]˶-VLnP+ Ls+^̸ h옖p„`hEl$Q"n&"š GĐ%e%pHnw6#EePdjMD (Q D ,(_xL^94߽ϿƼԧ"' % 6F@a$wzl!($aq]8L*Fċ0(4HH@)DYX PF)Qd/=^i! ,Up4*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_\iHͣKNسkνËOӫ_Ͼ}!(O_T' s& h`jk`00vE6\[RaWe0!$`ac|pBJ,1K JU`HA.8# ( KcQE,TEY|NViXf\v`)fkYi\@f\/Țp%I@LDƩ|ȟ )(T;p@c9蠐U衉.Zf ԤDT(JD0j驰֪>j#j*:_ [6{0)07+N.覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|C߀.n'7G.Wngw砇.褗n騧D! ,S H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI󟃛jɳϟ@ JѣH*]ʴӧPJJի@֯+(KVزhӪ] ۷p K'׺xE!u*y&8BE  l#'Jʠ'/JB(B~C|^RWڸsͻ  _MйmNm XkνËO B˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&䟒L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+6JAl;N! ,7)C҇gdDafH`ebkG 4\Ah|u auISBl3> C *h%"G%I͛8sɳϟ@ JѣH*]ʴ,.NJyԫXy.ׯZʴիXjݺ4xE06hq]˶۷p!Ԧi4߿ J @QeKLNw9@NRuuӨ/ g+x+۸sT^6 G1ȓ+i3ܥJUy,ν Bz|TW{`+š,bο`5a@\{:|ƥTF(!`QȅdބvVah(,0Xڅ2hcN@Ïi#(.QXsHVieG egT5i$RyhnET]$ZpyZX(AQ}R$5_25(&M4ⅥdGf[(U䆉fj$tLyPrTdBQ G鲦8lQl_ M-\X $=F4G (U a y?<ѭYh Іڥ^!jAzۚcJT&܊k/%x;UޱVO&0fp1R# ILg<qpqDy@pEC()$<7DF7_3FĜD=Gm&XDwFKu< 5@D@- qA5ADH0׀9@B% ]'Y&QBB `}d~ ]Am]@LܕA{\`  Aq۪@t`]1jDu\BPԓ1E')N~W%Lr5)p&TF??fXfp􋠑 (o4KA͂A )i"XD,p~`j@B&< %R ̠GHu%=D (ĆumsFBҰK6(l-erŗ\!`L-oHG!0#FPL(fR8;1[@֖?Pmkh "ty74 aϏl48>b*;y=];  H<2rB:R QA*9rx6 3U+Hy=Ja D5h P_Mg@q'y/I2L@8DS !HAt @:ˉ "K `@@FsV9 CAfMC/kHt9 64D r 9Vz#CPL @q&̩"X(!5kȶ 5?!ԤV!`=aU<.!R:QkOIڇki&q<7,\G1LSdr8 `h-{n]FyC, H)b!} BVZMNdz A;C۬XJ͆2T@bCgk &duH_Md$2M`/w I_C`; O1{ W$`HHg 7ė@WMȉչ^;)F?K`mB)BK>Q\́@Un'C2OyN~$ɫ̭jMES#eςص|hGJVs'c41|%,M&Ҝ|##!2xS $VSB15 O LxesFσ ״lem;"cfg1amcr Y iЎav{޺Kڼ7C9Җbժq́d-@֐]]I++N"*.o :#*ANqZ&\mP/"ou7 r#ԇNSb)Es+DЭNEx brV3̊fE{qia4w)#'n; $^1ИiOF]A6V8' ]jJ5`W!mC")t3>w/ej1bmRyA'ҳp gD'08sXw rրX"yq TDX 8}"xF (؂)V2+h3x6h8؃/%h>8'RHH"F(I؄WGNR8TXVxXZ\؅^`b8dXfxhjlIӆpr8tXvxxz|؇~8 {@u 5G~8Ex؉8Xx؊8Xx؋8XxȘʸ،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9Yyّ "9$Y&y(*,ْ.0294Y6y8y$òA#! A,VHJȰÇ#JHŋ3jȱǏ CqF UBɲ˗0cʜI͛MBhYZp JѣH*HZ իXjݪT`\9p3r]˶۷piaҸgܿ ,,ArrjG\#KLrG|$@--I=̲ӨS[%P3g3nUP۸s EHމw+_μ9H ?iPױ<#YU弻-w^fZ٫ ev(a {iU=-gVhTÆt (Qa#,0(4hچ6cd;)HE"oD&Hv  `$V5XrD@[2$A^F(NYV/t#9@@O!15P/3'^42M&xvHah5j$p )IFlXF@ bX~ke(*F$Gb$, T-rԔV ^Ⱥa.jEzI֖oǤQ=#ߊ)^]t& :Sc"TKfW`d33"TPq{PAA2E(uQP D)l = 2mC @P v 1QdD7Q\ER\ ]QPw9Ԅ4GfDaBa@DtyP7&%$϶Ai0$r! "x!! "P 7ypNKʄ=L8(V 0CĶ5A$.I [ƦFtgc>vDFme4쌋XnȲM"SajF lF188*i#JD1g AHǘ% " (&Q!3؆PINzc $gHpr-@F5{(0mg.Za8̲(ap1/x2]A9nsB*̐*WI,GmrL z:y$|?:Zu-Cvc*ذ&4'O_z,.),VJ`eԾ$:#蚛ϾLv"^ 7LKf\r.y=Prp6PY;\l 'h^2` [GYU@C 8Rݎ;Rmf54fխ~Mn>#g ;.If4⧯DEZ];nȵ_96f 62]$Z` & o61D[p6w7qAvdFV؅"t2dXfxhjl؆npr8tXvxxz|؇~8Xx؈8Xx؉8/Rx؊8XxxXh Xdrt،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9Yyّ "9$Y&y(*,ْ.0294Y6y8d:<ٓ>@B9DYFyHJLٔNPR9TYVyXZ\ٕ^`b9dYfyhjQy,l)$ND ;vi#! , #' ^"LM$v `eo% *(М$HrGsr)!#p#qډD ѩ#*\T Ģ0ŋ3jȱǏ CIɓ(S\ɲ˗0cʜI`8s^L0JѣL G$L AҪ!%L%f0^XϞ=- +˖A'p,W MAp]am.i*e_P÷ 3iu[/,3Yf(|MӨS^ͺװc˞M2۸se xNx=/X1μ7m ! C,R<C4-C31ŋ;2Ҝ0ݗɪ; H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣHX#ӧ~JիXjʵׯ`ÊKٳhӪ]˶-VL nP+ Ls+^̸ h옖p„ `hEl$Q!n&"š GĐ%e%pHnw6#Ce0djMD Q D ,(_xL^94߽Ͽ 8P&؏)=4XJ ! ,Up4*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_\iHͣKNسkνËOӫ_Ͼ}!(O_T' s& h`jk`00vE6\[RaWe0!$`ac|pBJ,1K JU`HA.8# ( KcQE,TEY|NViXf\v1dEhdlA)gY44D|gm ŸBD9S@*P&h@:@UJĥj;y L2Ɜ S**j:*C*p&UªKVKCpÍZD (ߖ㚫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|F߀.n'7G.Wngw砇.褗n騧ꬷNN ! ,S H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI󟃛jɳϟ@ JѣH*]ʴӧPJJի@֯+(KVزhӪ] ۷p K'׺xE!u*y&8BE  l#'Jʠ'/JB(B~C|^RWڸsͻ  _MйmNm XkνËO B˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&䟒L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+6JAl;N! ,7)C҇gdDafH`ebkG 4\Ah|u auISBl3> C *h%"G%I͛8sɳϟ@ JѣD"]ʴӧ@J恗Uj˻`vІ "! C,R<C4-C31ŋ;2Ҝ0ݗɪ; H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣHX#ӧ~JիXjʵׯ`ÊKٳhӪ]˶-VL nP+ Ls+^̸ h옖p„ `hEl$Q!n&"š GĐ%e%pHnw6#Ce0djMD ah+AD , xK^94߽Ͽ 8P&؏)=4XJ ! ,Up4*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ Jᣓ)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_\iHͣKNسkνËOӫ_Ͼ}!(O_T' s& h`jk`00vE6\[RaWe0!$`ac|pBJ 0K JU`HA.8裃*Lq:UDOPTViXf\vy^)&jYi\,\/Țp%I x|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w^ 6C@! ,S H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI󟃛jɳϟ@ JѣH*]ʴӧPJJիZו|2,ٳhӪFd۷sݻ:`JL!" ~ vMY DM L`bʠ1XP6ҨM;TsͻӚ%tPq↎O8>|9Ѝg\g=d!h&*МHZ`ZʴիXjݺ4xE06hq]˶۷p!Ԧi4߿ J @QeKLNw9@NRuuӨ/ g+x+۸sT^6 G1ȓ+i3ܥJUy,ν Bz|TW{`+š,bο`5a@\{:|ƥTF(!`QȅdބvVah(,0Xڅ2hcN@Ïi#(.QXsHVieG egT5i$RyhnET]$ZpyZX(AQ}R$5_25(&M4ⅥdGf[(U䆉fj$tLyPrTdBQ G鲦8lQl_ M-\X $=F4G (U a y?<ѭYh Іڥ^!jAzۚcJT&܊k/%x;UޱVO&0fp1R# ILg<qpqDy@pEC()$<7DF7_3FĜD=Gm&XDwFKu< 5@D@- qA5ADH0׀9@B% ]'Y&QBB `}d~ ]Am]@LܕA{\`  Aq۪@t`]1jDu\BPԓ1E')N~W%Lr5)p&TF??fXfp􋠑 (o4KA͂A )i"XD,p~`j@B&< %R ̠GHu%=D (ĆumsFBҰK6(l-erŗ\!`L-oHG!0#FPL(fR8;1[@֖?Pmkh "ty74 aϏl48>b*;y=];  H<2rB:R QA*9rx6 3U+Hy=Ja D5h P_Mg@q'y/I2L@8DS !HAt @:ˉ "K `@@FsV9 CAfMC/kHt9 64D r 9Vz#CPL @q&̩"X(!5kȶ 5?!ԤV!`=aU<.!R:QkOIڇki&q<7,\G1LSdr8 `h-{n]FyC, H)b!} BVZMNdz A;C۬XJ͆2T@bCgk &duH_Md$2M`/w I_C`; O1{ W$`HHg 7ė@WMȉչ^;)F?K`mB)BK>Q\́@Un'C2OyN~$ɫ̭jMES#eςص|hGJVs'c41|%,M&Ҝ|##!2xS $VSB15 O LxesFσ ״lem;"cfg1amcr Y iЎav{޺Kڼ7C9Җbժq́d-@֐]]I++N"*.o :#*ANqZ&\mP/"ou7 r#ԇNSb)Es+DЭNEx brV3̊fE{qia4w)#'n; $^1>oӮc$a, Vx(YߥЭT_!wB Wb?{˄o}C{W Nu.g*(+{6&DpdYO됅-u`d?_? k>{JS3߇a5-n3$$53[tjRyWZWwFT`fpx"*~[ANDO%")PqF4#QS3w0+,8h.603w<(TT=$h.HBxg ?xH؄i-vN8W4ȄTAb:hZ/…Edh#Qe.r/n!hr8tXvxxz|؇~8Xx؈U48Xx؉8؈߰HЊxu8!Gx؋8XxȘʸ،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9Yyّ "9$Y&y(*,ْ.0294Y6y8F:<ٓ>@B9DYFyHJLٔNPR9TYVyXNU2,\ٕ>b $! A,VHJȰÇ#JHŋ3jȱǏ CqF UBɲ˗0cʜI͛MBhYZp JѣH*HZ իXjݪT`\9p3r]˶۷piaҸgܿ ,,ArrjG\#KLrG|$@--I=̲ӨS[%P3g3nUP۸s EHމw+_μ9H ?iPױ<#YU弻-w^fZ٫ ev(a {iU=-gVhTÆt (Qa#,0(4hچ6cd;)HE"oD&Hv  `$V5XrD@[2$A^F(NYV/t#9@@O!15P/3'^42M&xvHah5j$p )IFlXF@ bX~ke(*F$Gb$, T-rԔV ^Ⱥa.jEzI֖oǤQ=#ߊ)^]t& :Sc"TKfW`d33"TPq{PAA2E(uQP D)l = 2mC @P v 1QdD7Q\ER\ ]QPw9Ԅ4GfDaBa@DtyP7&%$϶Ai0$r! "x!! "P 7ypNKʄ=L8(V 0CĶ5A$.I [ƦFtgc>vDFme4쌋XnȲM"SajF lF188*i#JD1g AHǘ% " (&Q!3؆PINzc $gHpr-@F5{(0mg.Za8̲(ap1/x2]A9nsB*̐*WI,GmrL z:y$|?:Zu-Cvc*ذ&4'O_z,.),VJ`eԾ$:#蚛ϾLv"^ 7LKf\r.y=Prp6PY;\l 'h^2` [GYU@C 8Rݎ;Rmf54fխ~Mn>#g ;.If4⧯DEZ];nȵ_96f @B8DXFxHJL؄NPR؄7VxXZ\؅^`b8dXfxh @nvot(tuXt|؇~8Xx؈8Xx؉8Xx؊8Xx؋8XxȘʸ،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9YyRx,$NC :&Y#! , #' ^"LM$v `eo% *(М$HrGsr)!#p#qډD ѩ#*\T Ģ0ŋ3jȱǏ CIɓ(S\ɲ˗0cʜI`8s^L0JѣL G$L AҪ!%L%f0^XϞ=- +˖A'o,W MAp]am.i*e_P÷ 3iu[/,3Yf(|MӨS^ͺװc˞M۸s⢻o<N, _6D! C,R<C4-C31ŋ;2Ҝ0ݗɪ; H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣHX#ӧ~JիXjʵׯ`ÊKٳhӪ]˶-VL nP+ Ls+^̸ h옖p„ `hEl$Q!n&"š GĐ%e%pHnw6#Ce0djMD Q D ,(_xL^94߽Ͽ 8P&؏)=4XJ ! ,Up4*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_\iHͣKNسkνËOӫ_Ͼ}!(O_T' s& h`jk`00vE6\[RaWe0!$`ac|pBJ,1K JU`HA.8# ( KcQE,TEY|NViXf\v`)fkYi\@f\/Țp%I@LDƩ|ȟ )(T;p@c9蠐U衉.Zf ԤDT(JD0j驰֪>j#j*:_ [6{0)07+N.覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|C߀.n'7G.Wngw砇.褗n騧D! ,S H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI󟃛jɳϟ@ JѣH*]ʴӧPJJի@֯+(KVزhӪ] ۷p K'׺xE!u*y&8BE  l#'Jʠ'/JB(B~C|^RWڸsͻ  _MйmNm XkνËO B˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&䟒L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+6JAl;N! ,7)C҇gdDafH`ebkG 4\Ah|u auISBl3> C *h%"G%I͛8sɳϟ@ JѣH*]ʴ,.NJyԫXy.ׯZʴիXjݺ4xE06hq]˶۷p!Ԧi4߿ J @QeKLNw9@NRuuӨ/ g+x+۸sT^6 G1ȓ+i3ܥJUy,ν Bz|TW{`+š,bο`5a@\{:|ƥTF(!`QȅdބvVah(,0Xڅ2hcN@Ïi#(.QXsHVieG egT5i$RyhnET]$ZpyZX(AQ}R$5_25(&M4ⅥdGf[(U䆉fj$tLyPrTdBQ G鲦8lQl_ M-\X $=F4G (U a y?<ѭYh Іڥ^!jAzۚcJT&܊k/%x;UޱVO&0fp1R# ILg<qpqDy@pEC()$<7DF7_3FĜD=Gm&XDwFKu< 5@D@- qA5ADH0׀9@B% ]'Y&QBB `}d~ ]Am]@LܕA{\`  Aq۪@t`]1jDu\BPԓ1E')N~W%Lr5)p&TF??fXfp􋠑 (o4KA͂A )i"XD,p~`j@B&< %R ̠GHu%=D (ĆumsFBҰK6(l-erŗ\!`L-oHG!0#FPL(fR8;1[@֖?Pmkh "ty74 aϏl48>b*;y=];  H<2rB:R QA*9rx6 3U+Hy=Ja D5h P_Mg@q'y/I2L@8DS !HAt @:ˉ "K `@@FsV9 CAfMC/kHt9 64D r 9Vz#CPL @q&̩"X(!5kȶ 5?!ԤV!`=aU<.!R:QkOIڇki&q<7,\G1LSdr8 `h-{n]FyC, H)b!} BVZMNdz A;C۬XJ͆2T@bCgk &duH_Md$2M`/w I_C`; O1{ W$`HHg 7ė@WMȉչ^;)F?K`mB)BK>Q\́@Un'C2OyN~$ɫ̭jMES#eςص|hGJVs'c41|%,M&Ҝ|##!2xS $VSB15 O LxesFσ ״lem;"cfg1amcr Y iЎav{޺Kڼ7C9Җbժq́d-@֐]]I++N"*.o :#*ANqZ&\mP/"ou7 r#ԇNSb)Es+DЭNEx brV3̊fE{qia4w)#'n; $^1ИiOF]AxCkQK-[<=^V/:Vӧ~w+SPV6n.L-tɲD ! [1,eaw47^XsVB>׿UlP BۆDR:g|<^˂3 ~yaDn%r߷EDTb X gDh38sXw8$bH*HE(4xW:h% @x&=nFX%Cx?xL#I؂9؄R"OSx2BK\"Z(]bXfxhjl؆npr8tXvxxz|؇~8!IXx؈8Xxj {GQ؉hv؊8Xx؋8XxȘʸ،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9Yyّ "9$Y&y(*,ْ.0294Y6y87:<ٓ>@B9DYFyHJLٔN~NH2,R9W7X#! A,VHJȰÇ#JHŋ3jȱǏ CqF UBɲ˗0cʜI͛MBhYZp JѣH*HZ իXjݪT`\9p3r]˶۷piaҸgܿ ,,ArrjG\#KLrG|$@--I=̲ӨS[%P3g3nUP۸s EHމw+_μ9H ?iPױ<#YU弻-w^fZ٫ ev(a {iU=-gVhTÆt (Qa#,0(4hچ6cd;)HE"oD&Hv  `$V5XrD@[2$A^F(NYV/t#9@@O!15P/3'^42M&xvHah5j$p )IFlXF@ bX~ke(*F$Gb$, T-rԔV ^Ⱥa.jEzI֖oǤQ=#ߊ)^]t& :Sc"TKfW`d33"TPq{PAA2E(uQP D)l = 2mC @P v 1QdD7Q\ER\ ]QPw9Ԅ4GfDaBa@DtyP7&%$϶Ai0$r! "x!! "P 7ypNKʄ=L8(V 0CĶ5A$.I [ƦFtgc>vDFme4쌋XnȲM"SajF lF188*i#JD1g AHǘ% " (&Q!3؆PINzc $gHpr-@F5{(0mg.Za8̲(ap1/x2]A9nsB*̐*WI,GmrL z:y$|?:Zu-Cvc*ذ&4'O_z,.),VJ`eԾ$:#蚛ϾLv"^ 7LKf\r.y=Prp6PY;\l 'h^2` [GYU@C 8Rݎ;Rmf54fխ~Mn>#g ;.If4⧯DEZ];nȵ_96f d|2Ĥ]w\.OjALaI61D[p6wWyAvdF 8 t+MG؁ "8$X&x(*,؂.0284X6x8:<؃>@B8DXFxHJL؄NPR8TXVxXUZ؅^`b8dXfxhjl؆npqXogoh xLNLJlHw8Xx؈8Xx؉8Xx؊8Xx؋8XxȘʸ،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9Yyّ "2A&9$;0)#%ْ5! , #' ^"LM$v `eo% *(М$HrGsr)!#p#qډD ѩ#*\T Ģ0ŋ3jȱǏ CIɓ(S\ɲ˗0cʜI`8s^L0JѣL G$L AҪ!%L%f0^XϞ=- +˖A'o,W MAp]am.i*e_P÷ 3iu[/,3Yf(|MӨS^ͺװc˞M۸s⢻o<N, _6D! C,R<C4-C31ŋ;2Ҝ0ݗɪ; H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣHX#ӧ~JիXjʵׯ`ÊKٳhӪ]˶-VL nP+ Ls+^̸ h옖p„ `hEl$Q!n&"š GĐ%e%pHnw6#Ce0djMD Q D ,(_xL^94߽Ͽ 8P&؏)=4XJ ! ,Up4*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_\iHͣKNسkνËOӫ_Ͼ}!(O_T' s& h`jk`00vE6\[RaWe0!$`ac|pBJ,1K JU`HA.8# ( KcQE,TEY|NViXf\v`)fkYi\@f\/Țp%I@LDƩ|ȟ )(T;p@c9蠐U衉.Zf ԤDT(JD0j驰֪>j#j*:_ [6{0)07+N.覫+k,l' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmx|C߀.n'7G.Wngw砇.褗n騧D! ,S H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI󟃛jɳϟ@ JѣH*]ʴӧPJJիZו|2,ٳhӪFd۷sݻ:`JL!" ~ vMY DM X`bʠ1XP6ҨM;Tsͻ 8qȏ'_燅KoŠسkwL;ӫGϾ㿗O?e?(h`D|`(ZtID(Vhfa)[<j ($h&EF(4h8 C *h%"G%I͛8sɳϟ@ JѣHv(SӧIP\j Ȩ/+Q^#˶-8m "! C,R<C4-C31ŋ;2Ҝ0ݗɪ; H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣHX#ӧ~JիXjʵׯ`ÊKٳhӪ]˶-VL nP+ Ls+^̸ h옖p„ `hEl$Q!n&"š GĐ%e%pHnw6#Ce0djMD ah+AD , xK^94߽Ͽ 8P&؏)=4XJ ! ,Up4*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_\iHͣKNسkνËOӫ_Ͼ}!(O_T' s& h`jk`00vE6\[RaWe0!$`ac|pBJ,1K JU`HA.8# ( KcQE,TEY|NViXf\v`dhUl))WwQxyv@ e1La衈&*2$ĤVjc@AD)O*ꨣB$ɟ JјJZ|jT I뮵kQ RY<k&Fk*+0Q,ܾ-݆ҷDkI&Nn+/F9@:ܘcN+0E;p/:+ C ?lBT|e1 w\̱,[,2: .< 3Aˬ+:s3 TWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o H T ! ,S H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI󟃛jɳϟ@ JѣH*]ʴӧPJJի0֯+(KVزhӪ] ۷p K'׺xE!u`*y&8BE  l#'Jʠ'/JB(B~C|^RWڸsͻ nJ+ēS# (H$-֩kOf<Җ) X«E=˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&䴒L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+"E8! @,VHJȰÇ#JHŋ3jȱǏ CQ*qBɲ˗0cʜI͛8h6 dJѣH*]$>ZʴիXjݺ4xE06hq]˶۷p!Ԧi4߿ J @QeKLNw9@NRuuӨ/ g+x+۸sT^6 G1ȓ+i3ܥJUy,ν Bz|TW{`+š,bο`5a@\{:|ƥTF(!`QȅdބvVah(,0Xڅ2hcN@Ïi#(.QXsHVieG egT5i$RyhnET]$ZpyZX(AQ}R$5_25(&M4ⅥdGf[(U䆉fj$tLyPrTdBQ G鲦8lQl_ M-\X $=F4G (U a y?<ѭYh Іڥ^!jAzۚcJT&܊k/%x;UޱVO&0fp1R# ILg<qpqDy@pEC()$<7DF7_3FĜD=Gm&XDwFKu< 5@D@- qA5ADH0׀9@B% ]'Y&QBB `}d~ ]Am]@LܕA{\`  Aq۪@t`]1jDu\BPԓ1E')N~W%Lr5)p&TF??fXfp􋠑 (o4KA͂A )i"XD,p~`j@B&< %R ̠GHu%=D (ĆumsFBҰK6(l-erŗ\!`L-oHG!0#FPL(fR8;1[@֖?Pmkh "ty74 aϏl48>b*;y=];  H<2rB:R QA*9rx6 3U+Hy=Ja D5h P_Mg@q'y/I2L@8DS !HAt @:ˉ "K `@@FsV9 CAfMC/kHt9 64D r 9Vz#CPL @q&̩"X(!5kȶ 5?!ԤV!`=aU<.!R:QkOIڇki&q<7,\G1LSdr8 `h-{n]FyC, H)b!} BVZMNdz A;C۬XJ͆2T@bCgk &duH_Md$2M`/w I_C`; O1{ W$`HHg 7ė@WMȉչ^;)F?K`mB)BK>Q\́@Un'C2OyN~$ɫ̭jMES#eςص|hGJVs'c41|%,M&Ҝ|##!2xS $VSB15 O LxesFσ ״lem;"cfg1amcr Y iЎav{޺Kڼ7C9Җbժq́d-@֐]]I++N"*.o :#*ANqZ&\mP/"ou7 r#ԇNSb)Es+DЭNEx brV3̊fE{qia4w)#'n; $^1/-3&vӮc$a,$a#x(YߥЭTcꁠ0׳^VCg_iz~!\!Zex[4q/H됅-u`\@kz/H9+M|LݮjЀCBRXkDAstjRyWZ׀qaDEwog}w3xNrmPb"8$X&x(*,؂.0284X6x8}WG:؃)bDB!3ru7HxPq@I۱E؄PX!FVFx\ Twb'hhd8@nX^؄|tH!e؆u~Z~|hE8xHvw`؈CIXx؉8Xx {v 8FXX؋8XxȘʸ،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9Yyّ "9$Y&y(*,ْ.0294Y6y8I:<ٓ>@B9DYFyHJLٔNPR9TYVyXZiNA2,^2ҕdi#! A,VHJȰÇ#JHŋ3jȱǏ CqF UBɲ˗0cʜI͛MBhYZp JѣH*HZ իXjݪT`\9p3r]˶۷piaҸgܿ ,,ArrjG\#KLrG|$@--I=̲ӨS[%P3g3nUP۸s EHމw+_μ9H ?iPױ<#YU弻-w^fZ٫ ev(a {iU=-gVhTÆt (Qa#,0(4hچ6cd;)HE"oD&Hv  `$V5XrD@[2$A^F(NYV/t#9@@O!15P/3'^42M&xvHah5j$p )IFlXF@ bX~ke(*F$Gb$, T-rԔV ^Ⱥa.jEzI֖oǤQ=#ߊ)^]t& :Sc"TKfW`d33"TPq{PAA2E(uQP D)l = 2mC @P v 1QdD7Q\ER\ ]QPw9Ԅ4GfDaBa@DtyP7&%$϶Ai0$r! "x!! "P 7ypNKʄ=L8(V 0CĶ5A$.I [ƦFtgc>vDFme4쌋XnȲM"SajF lF188*i#JD1g AHǘ% " (&Q!3؆PINzc $gHpr-@F5{(0mg.Za8̲(ap1/x2]A9nsB*̐*WI,GmrL z:y$|?:Zu-Cvc*ذ&4'O_z,.),VJ`eԾ$:#蚛ϾLv"^ 7LKf\r.y=Prp6PY;\l 'h^2` [GYU@C 8Rݎ;Rmf54fխ~Mn>#g ;.If4⧯DEZ];nȵ_96f d/5 w\.O/AZ0%d[p6wWdgvftZ]xHt}I؁ "8$X&x(*,؂.0284X6x8:<؃>@B8DXFxHJL؄NPR8TXVxRyX\؅^`b8dXfxhjl؆no8oGmh vLNjHw~8Xx؈8Xx؉8Xx؊8Xx؋8XxȘʸ،8Xxؘڸ؍8Xx蘎긎؎8Xx؏9Yy ِ9Yyّ yA$9$;0'##5! , #' ^"LM$v `eo% *(М$HrGsr)!#p#qډD ѩ#*\T Ģ0ŋ3jȱǏ CIɓ(S\ɲ˗0cʜI`8s^L0JѣL G$L AҪ!%L%f0^XϞ=- +˖A'o,W MAp]am.i*e_P÷ 3iu[/,3Yf(|MӨS^ͺװc˞M۸s⢻o<N, _6D! C,R<C4-C31ŋ;2Ҝ0ݗɪ; H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣHX#ӧ~JիXjʵׯ`ÊKٳhӪ]˶-VL nP+ Ls+^̸ h옖p„ `hEl$Q!n&"š GĐ%e%pHnw6#Ce0djMD Q D ,(_xL^94߽Ͽ 8P&؏)=4XJ ! ,Upd*\ȰÇ#JHŋ3jȱǏ CI$ J\ɲ˗0cʜI͏`ɳϟ@ J)*]ʴӧPvԫXjʵkƁIKٳ-]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k sύ6M'ӨK^ͺ>bnMms}ڻMxiA_|rͣKWWߝwەO^nӫg[}eAɟObH(h& 6F(Vhfv!CP@$h(W ⋋@X$e㏐Yyq@x6BVg!'}),Q '(U!RC›, 0L5r>UDYP衈&袌6裐F*餔BEgAt*\4 4ꪬ꫰*무j뭸뮼+pi%ȖT' Xgvd,ܤ394 BNdƢs7l.4".+.iDiTb´b' &ԱDoL/:+oCaKsƿ#[&w",,'+@ss:p -3m2RWmXg\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o H̐Ș! ,S H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI󟃛jɳϟ@ JѣH*]ʴӧPJJի@֯+(KVزhӪ] ۷p K'׺xE!u*y&8BE  l#'Jʠ'/JB(B~C|^RWڸsͻ  _MйmNm XkνËOӫ_Ͼ˟OϿ'`! h ""h` !~`EVfv ($h(,0(4h8<@)DiH&䯒L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6Ю$E8! ,7)C҇gdDafH`ebkG 4\Ah|u auISBl3> C *h%"G%I͛8sɳϟ@ JѣH*]ʴ,.NJyԫXy.ׯ integer -> double -> character. } \description{ These functions work exactly the same as \code{\link[purrr:map]{purrr::map()}} functions, but allow you to run the map in parallel. The documentation is adapted from both \code{purrr::map()}, and \code{future.apply::future_lapply()}, so look there for more details. } \examples{ library(furrr) library(dplyr) # for the pipe \donttest{ plan(multiprocess) } 1:10 \%>\% future_map(rnorm, n = 10) \%>\% future_map_dbl(mean) # If each element of the output is a data frame, use # future_map_dfr to row-bind them together: mtcars \%>\% split(.$cyl) \%>\% future_map(~ lm(mpg ~ wt, data = .x)) \%>\% future_map_dfr(~ as.data.frame(t(as.matrix(coef(.))))) # You can be explicit about what gets exported to the workers # To see this, use multisession (NOT multicore if on a Mac as the forked workers # still have access to this environment) \donttest{ plan(multisession) } x <- 1 y <- 2 # This will fail, y is not exported (no black magic occurs) # future_map(1, ~y, .options = future_options(globals = "x")) # y is exported future_map(1, ~y, .options = future_options(globals = "y")) } furrr/man/future_modify.Rd0000644000177700017770000000601113276355511016675 0ustar herbrandtherbrandt% Generated by roxygen2: do not edit by hand % Please edit documentation in R/future_modify.R \name{future_modify} \alias{future_modify} \alias{future_modify_at} \alias{future_modify_if} \title{Modify elements selectively via futures} \usage{ future_modify(.x, .f, ..., .progress = FALSE, .options = future_options()) future_modify_at(.x, .at, .f, ..., .progress = FALSE, .options = future_options()) future_modify_if(.x, .p, .f, ..., .progress = FALSE, .options = future_options()) } \arguments{ \item{.x}{A list or atomic vector.} \item{.f}{A function, formula, or atomic vector. If a \strong{function}, it is used as is. If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a function. There are three ways to refer to the arguments: \itemize{ \item For a single argument function, use \code{.} \item For a two argument function, use \code{.x} and \code{.y} \item For more arguments, use \code{..1}, \code{..2}, \code{..3} etc } This syntax allows you to create very compact anonymous functions. If \strong{character vector}, \strong{numeric vector}, or \strong{list}, it is converted to an extractor function. Character vectors index by name and numeric vectors index by position; use a list to index by position and name at different levels. Within a list, wrap strings in \code{\link[=get-attr]{get-attr()}} to extract named attributes. If a component is not present, the value of \code{.default} will be returned.} \item{...}{Additional arguments passed on to \code{.f}.} \item{.progress}{A logical, for whether or not to print a progress bar for multiprocess, multisession, and multicore plans.} \item{.options}{The \code{future} specific options to use with the workers. This must be the result from a call to \code{\link[=future_options]{future_options()}}.} \item{.at}{A character vector of names or a numeric vector of positions. Only those elements corresponding to \code{.at} will be modified.} \item{.p}{A single predicate function, a formula describing such a predicate function, or a logical vector of the same length as \code{.x}. Alternatively, if the elements of \code{.x} are themselves lists of objects, a string indicating the name of a logical element in the inner lists. Only those elements where \code{.p} evaluates to \code{TRUE} will be modified.} } \value{ An object the same class as .x } \description{ These functions work exactly the same as \code{\link[purrr:modify]{purrr::modify()}} functions, but allow you to modify in parallel. } \details{ From \code{purrr}) Since the transformation can alter the structure of the input; it's your responsibility to ensure that the transformation produces a valid output. For example, if you're modifying a data frame, \code{.f} must preserve the length of the input. } \examples{ library(furrr) library(dplyr) # for the pipe \donttest{ plan(multiprocess) } # Convert each col to character, in parallel future_modify(mtcars, as.character) iris \%>\% future_modify_if(is.factor, as.character) \%>\% str() mtcars \%>\% future_modify_at(c(1, 4, 5), as.character) \%>\% str() } furrr/man/future_invoke_map.Rd0000644000177700017770000000465713276355511017554 0ustar herbrandtherbrandt% Generated by roxygen2: do not edit by hand % Please edit documentation in R/future_invoke_map.R \name{future_invoke_map} \alias{future_invoke_map} \alias{future_invoke_map_chr} \alias{future_invoke_map_dbl} \alias{future_invoke_map_int} \alias{future_invoke_map_lgl} \alias{future_invoke_map_dfr} \alias{future_invoke_map_dfc} \title{Invoke functions via futures} \usage{ future_invoke_map(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) future_invoke_map_chr(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) future_invoke_map_dbl(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) future_invoke_map_int(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) future_invoke_map_lgl(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) future_invoke_map_dfr(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) future_invoke_map_dfc(.f, .x = list(NULL), ..., .env = NULL, .progress = FALSE, .options = future_options()) } \arguments{ \item{.f}{A list of functions.} \item{.x}{A list of argument-lists the same length as \code{.f} (or length 1). The default argument, \code{list(NULL)}, will be recycled to the same length as \code{.f}, and will call each function with no arguments (apart from any supplied in \code{...}.)} \item{...}{Additional arguments passed to each function.} \item{.env}{Environment in which \code{\link[=do.call]{do.call()}} should evaluate a constructed expression. This only matters if you pass as \code{.f} the name of a function rather than its value, or as \code{.x} symbols of objects rather than their values.} \item{.progress}{A logical, for whether or not to print a progress bar for multiprocess, multisession, and multicore plans.} \item{.options}{The \code{future} specific options to use with the workers. This must be the result from a call to \code{\link[=future_options]{future_options()}}.} } \description{ These functions work exactly the same as \code{\link[purrr:invoke_map]{purrr::invoke_map()}} functions, but allow you to invoke in parallel. } \examples{ \donttest{ plan(multiprocess) } df <- dplyr::tibble( f = c("runif", "rpois", "rnorm"), params = list( list(n = 10), list(n = 5, lambda = 10), list(n = 10, mean = -3, sd = 10) ) ) future_invoke_map(df$f, df$params) } furrr/man/fold.Rd0000644000177700017770000000274413274650566014757 0ustar herbrandtherbrandt% Generated by roxygen2: do not edit by hand % Please edit documentation in R/fold.R \name{fold} \alias{fold} \title{Efficient fold / reduce / accumulate / combine of a vector} \usage{ fold(x, f, left = TRUE, unname = TRUE, threshold = 1000L) } \arguments{ \item{x}{A vector.} \item{f}{A binary function, i.e. a function take takes two arguments.} \item{left}{If \code{TRUE}, vector is combined from the left (the first element), otherwise the right (the last element).} \item{unname}{If \code{TRUE}, function \code{f} is called as \code{f(unname(y), x[[ii]])}, otherwise as \code{f(y, x[[ii]])}, which may introduce name \code{"y"}. [[ii]: R:[ii [[ii]: R:[ii} \item{threshold}{An integer (>= 2) specifying the length where the recursive divide'and'conquer call will stop and incremental building of the partial value is performed. Using \code{threshold = +Inf} will disable recursive folding.} } \value{ A vector. } \description{ This function comes from the \code{future.apply} package. } \details{ In order for recursive folding to give the same results as non-recursive folding, binary function \code{f} must be \emph{associative} with itself, i.e. \code{f(f(x[[1]], x[[2]]), x[[3]])} equals \code{f(x[[1]], f(x[[2]]), x[[3]])}. This function is a more efficient (memory and speed) of [\code{base::Reduce(f, x, right = !left, accumulate = FALSE)}][base::Reduce], especially when \code{x} is long. [[1]: R:[1 [[2]: R:[2 [[3]: R:[3 [[1]: R:[1 [[2]: R:[2 [[3]: R:[3 [base::Reduce]: R:base::Reduce } furrr/man/future_map2.Rd0000644000177700017770000001124513276355511016252 0ustar herbrandtherbrandt% Generated by roxygen2: do not edit by hand % Please edit documentation in R/future_map2.R, R/future_pmap.R \name{future_map2} \alias{future_map2} \alias{future_map2_chr} \alias{future_map2_dbl} \alias{future_map2_int} \alias{future_map2_lgl} \alias{future_map2_dfr} \alias{future_map2_dfc} \alias{future_pmap} \alias{future_pmap_chr} \alias{future_pmap_dbl} \alias{future_pmap_int} \alias{future_pmap_lgl} \alias{future_pmap_dfr} \alias{future_pmap_dfc} \title{Map over multiple inputs simultaneously via futures} \usage{ future_map2(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) future_map2_chr(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) future_map2_dbl(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) future_map2_int(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) future_map2_lgl(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) future_map2_dfr(.x, .y, .f, ..., .id = NULL, .progress = FALSE, .options = future_options()) future_map2_dfc(.x, .y, .f, ..., .progress = FALSE, .options = future_options()) future_pmap(.l, .f, ..., .progress = FALSE, .options = future_options()) future_pmap_chr(.l, .f, ..., .progress = FALSE, .options = future_options()) future_pmap_dbl(.l, .f, ..., .progress = FALSE, .options = future_options()) future_pmap_int(.l, .f, ..., .progress = FALSE, .options = future_options()) future_pmap_lgl(.l, .f, ..., .progress = FALSE, .options = future_options()) future_pmap_dfr(.l, .f, ..., .id = NULL, .progress = FALSE, .options = future_options()) future_pmap_dfc(.l, .f, ..., .progress = FALSE, .options = future_options()) } \arguments{ \item{.x}{Vectors of the same length. A vector of length 1 will be recycled.} \item{.y}{Vectors of the same length. A vector of length 1 will be recycled.} \item{.f}{A function, formula, or atomic vector. If a \strong{function}, it is used as is. If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a function. There are three ways to refer to the arguments: \itemize{ \item For a single argument function, use \code{.} \item For a two argument function, use \code{.x} and \code{.y} \item For more arguments, use \code{..1}, \code{..2}, \code{..3} etc } This syntax allows you to create very compact anonymous functions. If \strong{character vector}, \strong{numeric vector}, or \strong{list}, it is converted to an extractor function. Character vectors index by name and numeric vectors index by position; use a list to index by position and name at different levels. Within a list, wrap strings in \code{\link[=get-attr]{get-attr()}} to extract named attributes. If a component is not present, the value of \code{.default} will be returned.} \item{...}{Additional arguments passed on to \code{.f}.} \item{.progress}{A logical, for whether or not to print a progress bar for multiprocess, multisession, and multicore plans.} \item{.options}{The \code{future} specific options to use with the workers. This must be the result from a call to \code{\link[=future_options]{future_options()}}.} \item{.id}{If not \code{NULL} a variable with this name will be created giving either the name or the index of the data frame.} \item{.l}{A list of lists. The length of \code{.l} determines the number of arguments that \code{.f} will be called with. List names will be used if present.} } \value{ An atomic vector, list, or data frame, depending on the suffix. Atomic vectors and lists will be named if \code{.x} or the first element of \code{.l} is named. If all input is length 0, the output will be length 0. If any input is length 1, it will be recycled to the length of the longest. } \description{ These functions work exactly the same as \code{\link[purrr:map2]{purrr::map2()}} functions, but allow you to run the map in parallel. Note that "parallel" as described in \code{purrr} is just saying that you are working with multiple inputs, and parallel in this case means that you can work on multiple inputs AND process them all in parallel as well. } \examples{ library(furrr) \donttest{ plan(multiprocess) } x <- list(1, 10, 100) y <- list(1, 2, 3) z <- list(5, 50, 500) future_map2(x, y, ~ .x + .y) # Split into pieces, fit model to each piece, then predict by_cyl <- split(mtcars, mtcars$cyl) mods <- future_map(by_cyl, ~ lm(mpg ~ wt, data = .)) future_map2(mods, by_cyl, predict) future_pmap(list(x, y, z), sum) # Matching arguments by position future_pmap(list(x, y, z), function(a, b ,c) a / (b + c)) # Vectorizing a function over multiple arguments df <- data.frame( x = c("apple", "banana", "cherry"), pattern = c("p", "n", "h"), replacement = c("x", "f", "q"), stringsAsFactors = FALSE ) future_pmap(df, gsub) future_pmap_chr(df, gsub) }