In a long-running function I am writing, I use bptry()
around bplapply()
so that I might re-do failed tasks (this is such a cool feature!). If any failures are detected, I want to return a custom error message to help the user re-run just the failed tasks, along with the partial results. Right now, I'm using warning()
to return the error message but this feels wrong because it really is an error and not a warning.
I feel like this might be a job for the bplist_error
or bperror
arguments of bptry()
, but I don't understand them well enough. Help doing this is much appreciated.
Here's a toy example.
suppressPackageStartupMessages(library(BiocParallel))
f <- function(x, BPPARAM, BPREDO = list()) {
val <- bptry(bplapply(x, sqrt, BPPARAM = BPPARAM, BPREDO = BPREDO))
if (!all(bpok(val))) {
# Feels like this should be an error rather than a warning, but I
# want to return the partial results so can't use stop() (I think)
warning("f() encountered errors: ",
sum(!bpok(val)), " of ", length(val), " tasks failed.\n",
"f() has returned partial results, including errors, ",
"for debugging purposes.\n",
"It may be possible to re-run just these failed tasks.\n",
"See help(\"f\")",
call. = FALSE)
return(val)
}
data.frame(x = unlist(x), val = unlist(val))
}
BPPARAM <- MulticoreParam(workers = 2, stop.on.error = FALSE)
# Good input
a <- f(list(1, 2, 3), BPPARAM = BPPARAM)
a
#> x val
#> 1 1 1.000000
#> 2 2 1.414214
#> 3 3 1.732051
# Bad input
b <- f(list(1, "2", 3), BPPARAM = BPPARAM)
#> Warning: f() encountered errors: 1 of 3 tasks failed.
#> f() has returned partial results, including errors, for debugging purposes.
#> It may be possible to re-run just these failed tasks.
#> See help("f")
b
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> <remote_error in FUN(...): non-numeric argument to mathematical function>
#> traceback() available as 'attr(x, "traceback")'
#>
#> [[3]]
#> [1] 1.732051
# Re-do failed task
c <- f(list(1, 2, 3), BPPARAM = BPPARAM, BPREDO = b)
#> resuming previous calculation ...
c
#> x val
#> 1 1 1.000000
#> 2 2 1.414214
#> 3 3 1.732051
BiocManager::valid()
#>
#> * sessionInfo()
#>
#> R version 3.5.0 (2018-04-23)
#> Platform: x86_64-apple-darwin15.6.0 (64-bit)
#> Running under: macOS Sierra 10.12.6
#>
#> Matrix products: default
#> BLAS: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRblas.0.dylib
#> LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib
#>
#> locale:
#> [1] en_AU.UTF-8/en_AU.UTF-8/en_AU.UTF-8/C/en_AU.UTF-8/en_AU.UTF-8
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] BiocParallel_1.15.3 repete_0.0.0.9011 devtools_1.13.5
#>
#> loaded via a namespace (and not attached):
#> [1] Rcpp_0.12.17 lobstr_0.0.0.9000 XVector_0.21.1
#> [4] magrittr_1.5 GenomicRanges_1.33.5 BiocGenerics_0.27.0
#> [7] zlibbioc_1.27.0 IRanges_2.15.13 munsell_0.4.3
#> [10] colorspace_1.3-2 rlang_0.2.0 stringr_1.3.1
#> [13] GenomeInfoDb_1.17.1 plyr_1.8.4 tools_3.5.0
#> [16] parallel_3.5.0 withr_2.1.2 fortunes_1.5-4
#> [19] digest_0.6.15 GenomeInfoDbData_1.1.0 BiocManager_1.29.45
#> [22] pryr_0.1.4 S4Vectors_0.19.5 bitops_1.0-6
#> [25] codetools_0.2-15 RCurl_1.95-4.10 memoise_1.1.0
#> [28] stringi_1.2.2 compiler_3.5.0 scales_0.5.0
#> [31] stats4_3.5.0
#>
#> Bioconductor version '3.8'
#>
#> * 0 packages out-of-date
#> * 1 packages too new
#>
#> create a valid installation with
#>
#> BiocManager::install("reprex", update = TRUE, ask = FALSE)
#> Warning message:
#> 0 packages out-of-date; 1 packages too new
Created on 2018-05-25 by the reprex package (v0.2.0).
Perhaps I need to think about this more. The design I had in mind is as follows.
f()
: A user-level function that takes a SummarizedExperiment. It breaks up the assay-data into chunks and uses BiocParallel to process these chunks in parallel. Finally, it constructs a new assay from these outputs that is added to the input SummarizedExperiment and returned..f()
: An internal function that operates on the chunks of data. The user should never need to call this directly.f()
is a long-running function (on the order of hours) because there are hundreds to thousands of data chunks to process. In my experience,.f()
will occasionally fail (e.g., due to a dropped node on the cluster). When any of the calls to.f()
have failed, then the call tof()
has also failed because the SummarizedExperiment cannot be constructed. So I feel like an error (rather than a warning or just a message) should be issued. When these types of failures occur, I'd like to offer the user an easy way to resume the computation without having to re-do all the work. And I'd like to offer this without the user having to know too much about BiocParallel.This is a mock-up of the desired user experience:
In the case of a warning, you could return an object that contains the info to re-do the calculation; probably the original SummarizedExperiment with metadata() (so that a user doing se <- f(se, ...) doesn't lose data), or maybe better 'remember' at the end of f() the info to redo as well as a signature (e.g., digest() of the original SummarizedExperiment), with the instruction to invoked f(..., BPREDO = se). Then throw a error.
(probably moved to bioc-devel mailing list territory...
Very good point re not clobbering result if invoked with
se <- f(se, ...)
. Hmm, there are more dragons here than I'd initially considered. I'll keep thinking on this (it may be more trouble than it's worth). Thanks for your advice, Martin.