From 5a405216b08751ddd2ef050b36095e93573b8ca4 Mon Sep 17 00:00:00 2001 From: Daniel Nachun Date: Tue, 22 Sep 2026 19:21:37 -0700 Subject: [PATCH 1/5] fix more funcation signature issues --- .github/environment/pixi.toml | 4 +- .github/recipe/recipe.yaml | 4 +- DESCRIPTION | 1 + NAMESPACE | 41 +- R/AllClasses.R | 9 +- R/AllGenerics.R | 4 + R/AnnotationMatrix.R | 46 +- R/ColocBoostResult.R | 60 +- R/ColocResult.R | 57 +- R/CtwasResult.R | 31 +- R/GenotypeHandle.R | 118 +- R/GwasFineMappingResult.R | 56 +- R/LdBlocks.R | 15 +- R/LdData.R | 11 + R/LdEigen.R | 31 +- R/LdScore.R | 45 +- R/LdStatistic.R | 25 +- R/MashPrior.R | 2 + R/MultiStudyQtlDataset.R | 16 +- R/QtlDataset.R | 353 ++- R/QtlFineMappingResult.R | 71 +- R/RangedTupleList.R | 19 +- R/SldscData.R | 18 +- R/causalInferencePipeline.R | 263 +- R/colocPipeline.R | 351 ++- R/colocboostPipeline.R | 303 ++- R/crossValidation.R | 17 +- R/ctwasPipeline.R | 267 ++- R/fineMappingPipeline.R | 2112 +++++++++++++---- R/fineMappingRow.R | 54 +- R/fineMappingWrappers.R | 1240 ++++++---- R/genotypeIo.R | 103 +- R/gwasSumStats.R | 25 +- R/h2EstimationWrappers.R | 51 +- R/jointEngine.R | 66 +- R/jointSpecification.R | 457 ++-- R/ld.R | 55 +- R/manifestLoaders.R | 108 +- R/mashPipeline.R | 69 +- R/mashWrapper.R | 22 +- R/pvalCombine.R | 37 +- R/qtlAssociationPostprocess.R | 20 +- R/qtlEnrichmentPipeline.R | 253 +- R/qtlSumStats.R | 60 +- R/regularizedRegressionWrappers.R | 684 ++++-- R/relatednessQc.R | 281 ++- R/sldscPostprocessingPipeline.R | 22 +- R/sldscWrapper.R | 46 +- R/sumstatsQc.R | 422 +++- R/tupleSelectors.R | 65 +- R/twasWeights.R | 350 ++- R/twasWeightsPipeline.R | 1280 +++++++--- R/twasWeightsRow.R | 27 +- R/variantId.R | 19 +- R/vcfWriter.R | 19 +- data/ctwasEstExample.rda | Bin 256672 -> 262116 bytes data/ctwasFinemapExample.rda | Bin 257572 -> 263108 bytes data/ctwasInputsExample.rda | Bin 255828 -> 261108 bytes inst/scripts/build_ctwas_examples.R | 20 +- man/GenotypeHandle.Rd | 6 +- man/bLassoWeights.Rd | 12 +- man/bayesAWeights.Rd | 5 +- man/bayesAlphabetWeights.Rd | 5 +- man/bayesBWeights.Rd | 13 +- man/bayesCWeights.Rd | 5 +- man/bayesLWeights.Rd | 5 +- man/bayesNWeights.Rd | 5 +- man/bayesRWeights.Rd | 5 +- man/bglrWeights.Rd | 14 +- man/causalInferencePipeline.Rd | 5 +- man/colocPipeline.Rd | 5 +- man/colocboostPipeline.Rd | 14 +- man/ctwasPipeline.Rd | 5 +- man/dot-penalizedRssWeights.Rd | 7 +- man/dprWeights.Rd | 11 +- man/estCtwasParam.Rd | 5 +- man/estimateH2.Rd | 4 + man/extractCsInfo.Rd | 6 +- man/extractTopPipInfo.Rd | 6 +- man/finemapCtwasRegions.Rd | 5 +- man/fitFsusie.Rd | 7 +- man/fitMvsusie.Rd | 5 +- man/fitMvsusieRss.Rd | 5 +- man/fsusieWrapper.Rd | 16 +- man/l0learnRssWeights.Rd | 5 +- man/l0learnWeights.Rd | 5 +- man/lassosumRssWeights.Rd | 5 +- man/ldMismatchQc.Rd | 6 +- man/mcpRssWeights.Rd | 5 +- man/mcpWeights.Rd | 5 +- man/mergeCtwasBoundaryRegions.Rd | 6 +- man/mrashRssWeights.Rd | 14 +- man/mrashWeights.Rd | 4 +- man/mrmashRssWeights.Rd | 5 +- man/mrmashWeights.Rd | 10 +- man/mrmashWrapper.Rd | 5 +- man/mvsusieRssWeights.Rd | 30 +- man/mvsusieWeights.Rd | 41 +- man/ncvregWeights.Rd | 5 +- man/prsCsWeights.Rd | 4 +- man/qtlEnrichmentPipeline.Rd | 8 +- man/readGenotypes.Rd | 7 +- man/scadRssWeights.Rd | 5 +- man/scadWeights.Rd | 5 +- man/screenCtwasRegions.Rd | 5 +- man/sdprWeights.Rd | 4 +- man/susieAshRssWeights.Rd | 16 +- man/susieAshWeights.Rd | 12 +- man/susieInfRssWeights.Rd | 16 +- man/susieInfWeights.Rd | 12 +- man/susieRssWeights.Rd | 19 +- man/susieWeights.Rd | 14 +- man/twasWeightsCv.Rd | 9 +- man/twasWeightsPipeline.Rd | 6 +- pixi.toml | 6 +- tests/testthat/test_AnnotationMatrix.R | 2 +- tests/testthat/test_ColocBoostResult.R | 11 +- tests/testthat/test_ColocResult.R | 7 +- tests/testthat/test_CtwasResult.R | 5 +- tests/testthat/test_GwasFineMappingResult.R | 9 +- tests/testthat/test_LdEigen.R | 48 +- tests/testthat/test_LdScore.R | 22 +- tests/testthat/test_LdStatistic.R | 6 +- tests/testthat/test_MashPrior.R | 8 + tests/testthat/test_MultiStudyQtlDataset.R | 13 +- tests/testthat/test_QtlDataset.R | 31 +- tests/testthat/test_QtlFineMappingResult.R | 7 +- tests/testthat/test_RangedTupleList.R | 9 +- tests/testthat/test_causalInferencePipeline.R | 100 +- tests/testthat/test_colocPipeline.R | 29 +- tests/testthat/test_colocboostPipeline.R | 34 +- tests/testthat/test_crossValidation.R | 8 +- tests/testthat/test_ctwasPipeline.R | 27 +- tests/testthat/test_fineMappingPipeline.R | 4 +- tests/testthat/test_fineMappingRow.R | 32 +- tests/testthat/test_fineMappingWrappers.R | 392 ++- tests/testthat/test_genotypeHandle.R | 2 +- tests/testthat/test_genotypeIo.R | 29 +- tests/testthat/test_h2EstimationWrappers.R | 2 +- tests/testthat/test_jointEngine.R | 157 +- tests/testthat/test_jointSpecification.R | 6 +- tests/testthat/test_ld.R | 42 +- tests/testthat/test_manifestLoaders.R | 59 +- tests/testthat/test_mashPipeline.R | 44 +- tests/testthat/test_mashWrapper.R | 24 + tests/testthat/test_pvalCombine.R | 12 +- .../testthat/test_qtlAssociationPostprocess.R | 17 +- tests/testthat/test_qtlEnrichmentPipeline.R | 17 +- tests/testthat/test_qtlSumStats.R | 10 +- .../test_regularizedRegressionWrappers.R | 115 +- tests/testthat/test_relatednessQc.R | 13 + tests/testthat/test_rrBayesAlphabet.R | 10 +- tests/testthat/test_rrDispatch.R | 101 +- tests/testthat/test_rrLassosum.R | 2 +- tests/testthat/test_rrMrAshRss.R | 2 +- tests/testthat/test_rrMrash.R | 6 +- tests/testthat/test_rrPenalizedRss.R | 2 +- tests/testthat/test_rrPrsCs.R | 12 +- tests/testthat/test_rrSdpr.R | 12 +- tests/testthat/test_rrSusie.R | 24 +- tests/testthat/test_sldscWrapper.R | 22 +- tests/testthat/test_sumstatsQc.R | 19 +- tests/testthat/test_tupleSelectors.R | 9 +- tests/testthat/test_twasWeights.R | 141 +- tests/testthat/test_twasWeightsPipeline.R | 80 +- tests/testthat/test_variantId.R | 38 +- vignettes/ctwas-pipeline.Rmd | 17 +- 167 files changed, 8673 insertions(+), 3862 deletions(-) diff --git a/.github/environment/pixi.toml b/.github/environment/pixi.toml index 76ed456d5..5d7f5865e 100644 --- a/.github/environment/pixi.toml +++ b/.github/environment/pixi.toml @@ -30,9 +30,11 @@ r45 = {features = ["r45"]} "bioconductor-bioccheck" = "*" "bioconductor-plyranges" = "*" "bioconductor-tidysummarizedexperiment" = "*" -"gcc" = "*" +# "gcc" = "*" "r-covr" = "*" +"r-decor" = "*" "r-devtools" = "*" +"r-goodpractice" = "*" "r-knitr" = "*" "r-lintr" = "*" "r-markdown" = "*" diff --git a/.github/recipe/recipe.yaml b/.github/recipe/recipe.yaml index b294c9095..99b9fa5f6 100644 --- a/.github/recipe/recipe.yaml +++ b/.github/recipe/recipe.yaml @@ -41,6 +41,7 @@ requirements: - r-base - r-bglr - r-bigsnpr + - r-checkmate - r-coda - r-coloc - r-colocboost @@ -48,7 +49,6 @@ requirements: - r-cpp11 - r-cpp11armadillo - r-ctwas - - r-decor - r-dplyr - r-flashier - r-fsusier @@ -109,6 +109,7 @@ requirements: - r-base - r-bglr - r-bigsnpr + - r-checkmate - r-coda - r-coloc - r-colocboost @@ -116,7 +117,6 @@ requirements: - r-cpp11 - r-cpp11armadillo - r-ctwas - - r-decor - r-dplyr - r-flashier - r-fsusier diff --git a/DESCRIPTION b/DESCRIPTION index 4459d282f..8c17c8bd3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -28,6 +28,7 @@ Imports: BiocGenerics, BiocParallel, Biostrings, + checkmate, coloc, colocboost, DelayedArray, diff --git a/NAMESPACE b/NAMESPACE index 09b8093d3..39935cc9a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -500,6 +500,41 @@ importFrom(SummarizedExperiment, rowRanges ) importFrom(archive,archive_read) +importFrom(checkmate, + assert, + assertCharacter, + assertClass, + assertCount, + assertDataFrame, + assertDirectoryExists, + assertFileExists, + assertFlag, + assertInt, + assertList, + assertLogical, + assertMatrix, + assertMultiClass, + assertNames, + assertNumber, + assertNumeric, + assertScalar, + assertString, + assertSubset, + assertVector, + checkAtomicVector, + checkCharacter, + checkClass, + checkDataFrame, + checkFileExists, + checkList, + checkMatrix, + checkNames, + checkNull, + checkNumeric, + checkString, + checkSubset, + makeAssertCollection +) importFrom(dplyr, across, add_count, @@ -559,8 +594,10 @@ importFrom(purrr, map_int, map_lgl, partial, + possibly, reduce, - set_names + set_names, + walk ) importFrom(quadprog,solve.QP) importFrom(readr, @@ -576,6 +613,7 @@ importFrom(rlang, arg_match, cnd_signal, inform, + try_fetch, warn ) importFrom(stats, @@ -653,7 +691,6 @@ importFrom(tools, importFrom(utils, combn, head, - modifyList, read.table, tail ) diff --git a/R/AllClasses.R b/R/AllClasses.R index 80c6aedb2..e3181943a 100644 --- a/R/AllClasses.R +++ b/R/AllClasses.R @@ -751,6 +751,7 @@ setMethod( # Resolve the single pinned entry and return its GRanges view (aggregating # across multiple entries requires type = "data.frame"). # @noRd +#' @importFrom rlang try_fetch .fmrbTopLociGranges <- function( x, study, @@ -761,7 +762,7 @@ setMethod( signalCutoff, minPurity ) { - sel <- tryCatch( + sel <- try_fetch( .fmrSelectEntry( x, study = study, @@ -770,7 +771,7 @@ setMethod( method = method, region = region ), - error = function(e) e + error = function(cnd) cnd ) if (inherits(sel, "error")) { msg <- glue( @@ -867,7 +868,9 @@ setMethod("resolveWeights", "FineMappingResultBase", function(x, ...) { # The per-variant weight of the row a selector pins. Defined on the # collection because that is what getFineMappingResult() now returns; the # body is the per-row primitive, so the two cannot drift. - .fmrRowResolveWeights(.fmrSelectEntry(x, ...), ...) + # `...` is the row selector; it is consumed by .fmrSelectEntry and has + # no meaning to the per-row primitive, so it is not forwarded twice. + .fmrRowResolveWeights(.fmrSelectEntry(x, ...)) }) #' @rdname getVariantIds diff --git a/R/AllGenerics.R b/R/AllGenerics.R index f69815b4c..a3ac7f968 100644 --- a/R/AllGenerics.R +++ b/R/AllGenerics.R @@ -50,6 +50,8 @@ NULL #' @param annotations An \code{AnnotationMatrix} object, or NULL for #' unstratified estimation. #' @param local Logical, whether to compute per-block local estimates. +#' @param estimatorArgs Optional named list of estimator-specific options +#' (\code{lambda} for lder / gldsc / hdl, \code{nIter} for sldsc). #' @param ... Additional method-specific arguments. #' @param study Character (length 1) or \code{NULL}. Restrict the selection to #' this study; \code{NULL} matches all studies. @@ -139,6 +141,8 @@ setGeneric("computeLdScores", function(ldRef, annotations = NULL, ...) { #' inferred from file extension. #' @param ... The keyword source arguments described above, plus any further #' arguments forwarded to the format-specific reader. +#' @param vcfArgs Optional named list of arguments forwarded to +#' \code{VariantAnnotation::readVcf} when the source is a VCF. #' @return A \code{RangedSummarizedExperiment} of variants x samples. #' @seealso \code{\link{computeLd}} #' @examples diff --git a/R/AnnotationMatrix.R b/R/AnnotationMatrix.R index 2f100a298..eb7d84b9c 100644 --- a/R/AnnotationMatrix.R +++ b/R/AnnotationMatrix.R @@ -38,26 +38,35 @@ setClass( # The tier/type vocabulary is the only thing left to check: the SNP-by- # annotation shape is now enforced by SummarizedExperiment itself. # @noRd +#' @importFrom checkmate makeAssertCollection assertNames assertSubset .validateAnnotationMatrix <- function(object) { - errors <- character() + coll <- makeAssertCollection() cd <- SummarizedExperiment::colData(object) - required <- c("name", "tier", "type") - if (!all(is_in(required, colnames(cd)))) { - return("annotationMeta must have columns: name, tier, type") - } - if (!all(is_in(cd$tier, c("baseline", "candidate")))) { - errors <- c( - errors, - "annotationMeta$tier must be 'baseline' or 'candidate'" - ) - } - if (!all(is_in(cd$type, c("binary", "continuous")))) { - errors <- c( - errors, - "annotationMeta$type must be 'binary' or 'continuous'" - ) + assertNames( + colnames(cd), + must.include = c("name", "tier", "type"), + what = "colnames", + .var.name = "annotationMeta", + add = coll + ) + # The value checks below read those columns; without them they would + # report the consequence rather than the cause. + if (!coll$isEmpty()) { + return(coll$getMessages()) } - if (length(errors) == 0) TRUE else errors + assertSubset( + cd$tier, + c("baseline", "candidate"), + .var.name = "annotationMeta$tier", + add = coll + ) + assertSubset( + cd$type, + c("binary", "continuous"), + .var.name = "annotationMeta$type", + add = coll + ) + coll$getMessages() } #' @rdname show-methods @@ -114,6 +123,7 @@ setMethod("getGenome", "AnnotationMatrix", function(x, ...) { if (!all(is_in(requiredCols, colnames(annotationMeta)))) { abort("annotationMeta must have columns: name, tier, type") } + # NOT assertMatrix: `annotations` may be a sparse Matrix, not a base one. if (ncol(annotations) != nrow(annotationMeta)) { abort(glue( "`annotations` has {ncol(annotations)} column(s) for ", @@ -187,7 +197,9 @@ AnnotationMatrix <- function( # rows, the assay and the per-annotation table aligned, where the previous # implementation rebuilt the object from three separately-subset pieces. # @noRd +#' @importFrom checkmate assertClass .annotTier <- function(annot, tier) { + assertClass(annot, "AnnotationMatrix") annot[, SummarizedExperiment::colData(annot)$tier == tier] } diff --git a/R/ColocBoostResult.R b/R/ColocBoostResult.R index 203e23290..0638f72e0 100644 --- a/R/ColocBoostResult.R +++ b/R/ColocBoostResult.R @@ -90,26 +90,25 @@ methods::setValidity("ColocBoostResult", function(object) { } # @noRd +#' @importFrom checkmate makeAssertCollection assertNames checkNames +#' @importFrom checkmate checkSubset .validateColocBoostResult <- function(object) { - errors <- .cbrCheckRequiredCols(object) - if (length(errors) == 0L) { - errors <- c( - .cbrCheckVcpColumn(object), - .cbrCheckOutcomeInfo(object) - ) - } - if (length(errors) == 0L) TRUE else errors -} - -# @noRd -.cbrCheckRequiredCols <- function(object) { - md <- mcols(object, use.names = FALSE) - have <- if (is.null(md)) character(0) else colnames(md) - missingCols <- setdiff(.cbrRequiredCols(), have) - if (length(missingCols) > 0L) { - return(str_c("missing columns: ", str_flatten(missingCols, ", "))) + coll <- makeAssertCollection() + assertNames( + colnames(mcols(object, use.names = FALSE)) %||% character(0), + must.include = .cbrRequiredCols(), + what = "colnames", + .var.name = "mcols", + add = coll + ) + # The checks below read those columns; running them on an object missing + # them reports the consequence rather than the cause. + if (!coll$isEmpty()) { + return(coll$getMessages()) } - NULL + coll$push(.cbrCheckVcpColumn(object)) + coll$push(.cbrCheckOutcomeInfo(object)) + coll$getMessages() } # The per-variant layer is the point of the class, exactly as SNP.PP.H4 is for @@ -145,28 +144,23 @@ methods::setValidity("ColocBoostResult", function(object) { if (nrow(info) == 0L) { return(NULL) } - missingCols <- setdiff( - c("name", "study", "context", "trait", "dataForm"), - colnames(info) + cols <- checkNames( + colnames(info), + must.include = c("name", "study", "context", "trait", "dataForm"), + what = "colnames" ) - if (length(missingCols) > 0L) { - return(str_c( - "outcomeInfo is missing columns: ", - str_flatten(missingCols, ", ") - )) + if (!isTRUE(cols)) { + return(str_c("outcomeInfo is missing columns: ", cols)) } if (length(object) == 0L) { return(NULL) } named <- unique(unlist(mcols(object, use.names = FALSE)$outcomes)) - unknown <- setdiff(named, as.character(info$name)) - if (length(unknown) == 0L) { + resolved <- checkSubset(named, as.character(info$name)) + if (isTRUE(resolved)) { return(NULL) } - str_c( - "outcome(s) not present in outcomeInfo: ", - str_flatten(utils::head(unknown, 5L), ", ") - ) + str_c("outcome(s) not in outcomeInfo: ", resolved) } # ---- accessors -------------------------------------------------------------- @@ -257,7 +251,7 @@ ColocBoostResult <- function( gwasStudy = gwasStudy, includeUncolocalized = includeUncolocalized ) - rows <- unlist(rows, recursive = FALSE, use.names = FALSE) + rows <- unname(list_flatten(rows)) .cbrAssemble( rows, outcomeInfo = as.data.frame(outcomeInfo), diff --git a/R/ColocResult.R b/R/ColocResult.R index 60b3fd18d..4c46d67bb 100644 --- a/R/ColocResult.R +++ b/R/ColocResult.R @@ -78,15 +78,24 @@ methods::setValidity("ColocResult", function(object) { }) # @noRd +#' @importFrom checkmate makeAssertCollection assertNames checkNames .validateColocResult <- function(object) { - errors <- .crCheckRequiredCols(object) - if (length(errors) == 0L) { - errors <- c( - .crCheckPpColumns(object), - .crCheckVariantColumn(object) - ) + coll <- makeAssertCollection() + assertNames( + colnames(mcols(object, use.names = FALSE)) %||% character(0), + must.include = .crRequiredCols(), + what = "colnames", + .var.name = "mcols", + add = coll + ) + # The checks below read those columns; running them on an object missing + # them reports the consequence rather than the cause. + if (!coll$isEmpty()) { + return(coll$getMessages()) } - if (length(errors) == 0L) TRUE else errors + coll$push(.crCheckPpColumns(object)) + coll$push(.crCheckVariantColumn(object)) + coll$getMessages() } # @noRd @@ -112,28 +121,19 @@ methods::setValidity("ColocResult", function(object) { str_c("PP.H", 0:4, ".abf") } -# @noRd -.crCheckRequiredCols <- function(object) { - md <- mcols(object, use.names = FALSE) - have <- if (is.null(md)) character(0) else colnames(md) - missingCols <- setdiff(.crRequiredCols(), have) - if (length(missingCols) > 0L) { - return(str_c("missing columns: ", str_flatten(missingCols, ", "))) - } - NULL -} - +# The posterior columns are a distinct contract from the identity columns +# above, so they keep their own wording. # @noRd .crCheckPpColumns <- function(object) { - md <- mcols(object, use.names = FALSE) - missingCols <- setdiff(.crPpCols(), colnames(md)) - if (length(missingCols) > 0L) { - return(str_c( - "missing posterior columns: ", - str_flatten(missingCols, ", ") - )) + res <- checkNames( + colnames(mcols(object, use.names = FALSE)), + must.include = .crPpCols(), + what = "colnames" + ) + if (isTRUE(res)) { + return(NULL) } - NULL + str_c("missing posterior columns: ", res) } # The per-variant layer is the whole point of the class, so an element without @@ -532,19 +532,20 @@ setMethod( } # @noRd +#' @importFrom rlang try_fetch .crPurityOne <- function(ids, ldSketch) { # A singleton set has no pair to correlate, and susie treats it as pure. if (length(ids) < 2L) { return(1) } - ld <- tryCatch( + ld <- try_fetch( .ldFromSketch( ldSketch, ids, label = "getColocCredibleSets", onMissing = "drop" ), - error = function(e) NULL + error = function(cnd) NULL ) if (is.null(ld) || nrow(ld) < 2L) { return(NA_real_) diff --git a/R/CtwasResult.R b/R/CtwasResult.R index b14e89a04..5fe5649ea 100644 --- a/R/CtwasResult.R +++ b/R/CtwasResult.R @@ -37,22 +37,23 @@ setClass("CtwasResult", contains = "DFrame", validity = function(object) { # Collect all contract violations (empty vector = valid). Entry checks run only # once the required columns are present. # @noRd +#' @importFrom checkmate makeAssertCollection assertNames .validateCtwasResult <- function(object) { - errors <- .ctwasResCheckRequiredCols(object) - if (length(errors) == 0L) { - errors <- .ctwasResCheckEntries(object) - } - if (length(errors) == 0L) TRUE else errors -} - -# @noRd -.ctwasResCheckRequiredCols <- function(object) { - required <- c("gwasStudy", "study", "context", "method", "entry") - missingCols <- setdiff(required, names(object)) - if (length(missingCols) > 0L) { - return(str_c("missing columns: ", str_flatten(missingCols, ", "))) + coll <- makeAssertCollection() + assertNames( + names(object), + must.include = c("gwasStudy", "study", "context", "method", "entry"), + what = "colnames", + .var.name = "columns", + add = coll + ) + # The checks below read those columns; running them on an object missing + # them reports the consequence rather than the cause. + if (!coll$isEmpty()) { + return(coll$getMessages()) } - NULL + coll$push(.ctwasResCheckEntries(object)) + coll$getMessages() } # @noRd @@ -89,7 +90,7 @@ setClass("CtwasResult", contains = "DFrame", validity = function(object) { # @noRd .ctwasResCheckJointCols <- function(object, jointCols) { - unlist(compact(map(jointCols, .ctwasResJointColError, object = object))) + list_c(compact(map(jointCols, .ctwasResJointColError, object = object))) } # @noRd diff --git a/R/GenotypeHandle.R b/R/GenotypeHandle.R index 8a5bc7c4e..f065a9796 100644 --- a/R/GenotypeHandle.R +++ b/R/GenotypeHandle.R @@ -166,7 +166,8 @@ setMethod("show", "GenotypeHandle", function(object) { #' shard is read (so the caller's own absence check can report the mismatch). #' Only meaningful with \code{genoMeta}; supplying it with any other source is #' an error (a single-file panel has no per-chromosome shards to skip). -#' @param ... Additional arguments forwarded to the format-specific reader. +#' @param vcfArgs Optional named list of arguments forwarded to +#' \code{VariantAnnotation::readVcf} when the source is a VCF. #' @return A \code{GenotypeHandle} object. #' @keywords internal GenotypeHandle <- function( @@ -183,47 +184,64 @@ GenotypeHandle <- function( region = NULL, genoMeta = NULL, chroms = NULL, - ... + format = NULL, + vcfArgs = list() ) { - p <- as.list(environment()) - flags <- .ghValidateArgs(p) - sources <- .ghResolveSources(p, flags) + flags <- .ghValidateArgs( + bed = bed, + bim = bim, + fam = fam, + pgen = pgen, + pvar = pvar, + psam = psam, + ldMeta = ldMeta, + region = region + ) + sources <- .ghResolveSources( + path = path, + plink1Prefix = plink1Prefix, + plink2Prefix = plink2Prefix, + ldMeta = ldMeta, + genoMeta = genoMeta, + chroms = chroms, + flags = flags + ) if (sources[["path"]]) { - return(.readGenotypeHandle(path, ...)) + return(.readGenotypeHandle(path, format = format, vcfArgs = vcfArgs)) } if (sources[["plink1Prefix"]]) { - return(.makePlink1Handle(plink1Prefix, ...)) + return(.makePlink1Handle(plink1Prefix)) } if (sources[["plink2Prefix"]]) { - return(.makePlink2Handle(plink2Prefix, ...)) + return(.makePlink2Handle(plink2Prefix)) } if (sources[["plink1Triplet"]]) { - return(.genotypeHandleFromPlink1Triplet(bed, bim, fam, ...)) + return(.genotypeHandleFromPlink1Triplet(bed, bim, fam)) } if (sources[["plink2Triplet"]]) { - return(.genotypeHandleFromPlink2Triplet(pgen, pvar, psam, ...)) + return(.genotypeHandleFromPlink2Triplet(pgen, pvar, psam)) } if (sources[["ldMeta"]]) { - return(.genotypeHandleFromLdMeta(ldMeta, region, ...)) + return(.genotypeHandleFromLdMeta(ldMeta, region, vcfArgs = vcfArgs)) } # nSources == 1 is enforced above, so the only remaining source is genoMeta. - .genotypeHandleFromChromMeta(genoMeta, chroms = chroms, ...) + .genotypeHandleFromChromMeta(genoMeta, chroms = chroms, format = format) } # Validate the bed/bim/fam + pgen/pvar/psam triplet completeness and the # ldMeta<->region coupling. Returns list(bedComplete, pgenComplete). # @noRd -.ghValidateArgs <- function(p) { - bedComplete <- .ghTrioComplete(p$bed, p$bim, p$fam, "bed/bim/fam") - pgenComplete <- .ghTrioComplete(p$pgen, p$pvar, p$psam, "pgen/pvar/psam") - if (!is.null(p$ldMeta) && is.null(p$region)) { +.ghValidateArgs <- function(bed, bim, fam, pgen, pvar, psam, ldMeta, region) { + bedComplete <- .ghTrioComplete(bed, bim, fam, "bed/bim/fam") + pgenComplete <- .ghTrioComplete(pgen, pvar, psam, "pgen/pvar/psam") + if (!is.null(ldMeta) && is.null(region)) { msg <- glue( "`ldMeta` requires a `region` (a 'chr:start-end' string or a ", "one-row data.frame with chrom/start/end)." ) abort(msg) } - if (is.null(p$ldMeta) && !is.null(p$region)) { + if (is.null(ldMeta) && !is.null(region)) { abort("`region` is only meaningful when `ldMeta` is supplied.") } list(bedComplete = bedComplete, pgenComplete = pgenComplete) @@ -246,15 +264,23 @@ GenotypeHandle <- function( # Build the exactly-one-source indicator vector; error unless exactly one input # source was supplied, and gate `chroms` to the genoMeta path. # @noRd -.ghResolveSources <- function(p, flags) { +.ghResolveSources <- function( + path, + plink1Prefix, + plink2Prefix, + ldMeta, + genoMeta, + chroms, + flags +) { sources <- c( - path = !is.null(p$path), - plink1Prefix = !is.null(p$plink1Prefix), - plink2Prefix = !is.null(p$plink2Prefix), + path = !is.null(path), + plink1Prefix = !is.null(plink1Prefix), + plink2Prefix = !is.null(plink2Prefix), plink1Triplet = flags$bedComplete, plink2Triplet = flags$pgenComplete, - ldMeta = !is.null(p$ldMeta), - genoMeta = !is.null(p$genoMeta) + ldMeta = !is.null(ldMeta), + genoMeta = !is.null(genoMeta) ) if (sum(sources) != 1L) { nSrc <- sum(sources) @@ -265,7 +291,7 @@ GenotypeHandle <- function( ) abort(msg) } - if (!is.null(p$chroms) && !sources[["genoMeta"]]) { + if (!is.null(chroms) && !sources[["genoMeta"]]) { msg <- glue( "`chroms` restricts which per-chromosome shards are read and is ", "only supported with `genoMeta` (a single-file panel has no ", @@ -276,9 +302,9 @@ GenotypeHandle <- function( sources } -.genotypeHandleFromLdMeta <- function(ldMeta, region, ...) { +.genotypeHandleFromLdMeta <- function(ldMeta, region, vcfArgs = list()) { ldPath <- .ghResolveLdPath(ldMeta, region) - .ghLdPathToHandle(ldPath, ...) + .ghLdPathToHandle(ldPath, vcfArgs = vcfArgs) } # Resolve the single genotype-payload path for `region` from an LD meta, @@ -320,24 +346,22 @@ GenotypeHandle <- function( # Dispatch a resolved genotype path to the format-specific reader by extension. # @noRd -.ghLdPathToHandle <- function(ldPath, ...) { +.ghLdPathToHandle <- function(ldPath, vcfArgs = list()) { lower <- str_to_lower(ldPath) if (str_detect(lower, "\\.vcf(\\.b?gz)?$") || str_ends(lower, "\\.bcf")) { - return(.readGenotypeHandle(ldPath, format = "vcf", ...)) + return(.readGenotypeHandle(ldPath, format = "vcf", vcfArgs = vcfArgs)) } if (str_ends(lower, "\\.gds")) { - return(.readGenotypeHandle(ldPath, format = "gds", ...)) + return(.readGenotypeHandle(ldPath, format = "gds")) } if (str_ends(lower, "\\.bed")) { return(.makePlink1Handle( - str_remove(ldPath, regex("\\.bed$", ignore_case = TRUE)), - ... + str_remove(ldPath, regex("\\.bed$", ignore_case = TRUE)) )) } if (str_ends(lower, "\\.pgen")) { return(.makePlink2Handle( - str_remove(ldPath, regex("\\.pgen$", ignore_case = TRUE)), - ... + str_remove(ldPath, regex("\\.pgen$", ignore_case = TRUE)) )) } msg <- glue( @@ -348,12 +372,11 @@ GenotypeHandle <- function( abort(msg) } -.genotypeHandleFromPlink1Triplet <- function(bed, bim, fam, ...) { - for (f in list(bed = bed, bim = bim, fam = fam)) { - if (!is.character(f) || length(f) != 1L) { - abort("Each of `bed`, `bim`, `fam` must be a single file path.") - } - } +#' @importFrom checkmate assertString +.genotypeHandleFromPlink1Triplet <- function(bed, bim, fam) { + assertString(bed) + assertString(bim) + assertString(fam) stems <- c( bed = file_path_sans_ext(bed), bim = file_path_sans_ext(bim), @@ -371,10 +394,10 @@ GenotypeHandle <- function( ) abort(msg) } - .makePlink1Handle(unname(stems[1L]), ...) + .makePlink1Handle(unname(stems[1L])) } -.genotypeHandleFromPlink2Triplet <- function(pgen, pvar, psam, ...) { +.genotypeHandleFromPlink2Triplet <- function(pgen, pvar, psam) { for (f in list(pgen = pgen, pvar = pvar, psam = psam)) { if (!is.character(f) || length(f) != 1L) { abort("Each of `pgen`, `pvar`, `psam` must be a single file path.") @@ -398,7 +421,7 @@ GenotypeHandle <- function( ) abort(msg) } - .makePlink2Handle(unname(stems[1L]), ...) + .makePlink2Handle(unname(stems[1L])) } # --------------------------------------------------------------------------- @@ -414,8 +437,10 @@ GenotypeHandle <- function( # pecotmr). Such objects have no `chromPaths` slot, so a direct `@` access # errors; treat them as single-file handles. #' @keywords internal +#' @importFrom purrr possibly .genotypeChromPaths <- function(handle) { - tryCatch(getChromPaths(handle), error = function(e) character(0)) + # A handle with no chromosome paths is an ordinary outcome, not a fault. + possibly(getChromPaths, otherwise = character(0))(handle) } # Case-insensitive match of the first of `aliases` present in `cols`; falls @@ -578,8 +603,11 @@ GenotypeHandle <- function( # the other per-chromosome files are never opened, which is the I/O win when a # genome-wide panel backs summary statistics on only a few chromosomes. #' @keywords internal -.genotypeHandleFromChromMeta <- function(genoMeta, chroms = NULL, ...) { - format <- list(...)$format +.genotypeHandleFromChromMeta <- function( + genoMeta, + chroms = NULL, + format = NULL +) { parsed <- .parseChromMeta(genoMeta) if (nrow(parsed) == 0L) { abort( diff --git a/R/GwasFineMappingResult.R b/R/GwasFineMappingResult.R index 1792577f0..050faa144 100644 --- a/R/GwasFineMappingResult.R +++ b/R/GwasFineMappingResult.R @@ -35,23 +35,23 @@ setClass( # ---- GwasFineMappingResult validity helpers -------------------------------- # @noRd +#' @importFrom checkmate makeAssertCollection assertNames checkNames .validateGwasFineMappingResult <- function(object) { - errors <- .gfmrCheckRequiredCols(object) - if (length(errors) == 0L) { - errors <- .gfmrCheckEntries(object) + coll <- makeAssertCollection() + assertNames( + .tupleColumnNames(object), + must.include = c("study", "method"), + what = "colnames", + .var.name = "mcols", + add = coll + ) + # The checks below read those columns; running them on an object missing + # them reports the consequence rather than the cause. + if (!coll$isEmpty()) { + return(coll$getMessages()) } - errors <- c(errors, .gfmrCheckLdSketch(object)) - if (length(errors) == 0L) TRUE else errors -} - -# @noRd -.gfmrCheckRequiredCols <- function(object) { - required <- c("study", "method") - missingCols <- setdiff(required, .tupleColumnNames(object)) - if (length(missingCols) > 0L) { - return(str_c("missing columns: ", str_flatten(missingCols, ", "))) - } - NULL + coll$push(.gfmrCheckEntries(object)) + coll$getMessages() } # @noRd @@ -67,23 +67,17 @@ setClass( .gfmrCheckEntryLength <- function(object) { # The variants and their topLoci ARE the elements now, so what is left to # check is that the fit payload columns are present and parallel. - missingCols <- setdiff( - c("susieFit", "cvResult"), - .tupleColumnNames(object) + res <- checkNames( + .tupleColumnNames(object), + must.include = c("susieFit", "cvResult"), + what = "colnames" ) - if (length(missingCols) > 0L) { - return(str_c( - "missing entry payload columns: ", - str_flatten(missingCols, ", ") - )) + if (isTRUE(res)) { + return(NULL) } - NULL + str_c("missing entry payload columns: ", res) } -# @noRd - -# @noRd - # @noRd .gfmrCheckTupleUniqueness <- function(object) { # Keyed on the element's RANGE rather than on a region_id label. The range @@ -112,12 +106,6 @@ setClass( .tupleColumn(object, cn) } -# @noRd -.gfmrCheckLdSketch <- function(object) { - # The slot's class union enforces the type; nothing to check. - NULL -} - #' @title Create a GwasFineMappingResult Collection #' @description Construct a \code{GwasFineMappingResult} collection from diff --git a/R/LdBlocks.R b/R/LdBlocks.R index cef4a5128..c1ac256fa 100644 --- a/R/LdBlocks.R +++ b/R/LdBlocks.R @@ -36,15 +36,14 @@ NULL # One block per row. `blockId` is carried into mcols when present so the key # survives; without it the splitter falls back to the block's coordinates. # @noRd +#' @importFrom checkmate assertNames .ldBlockTableToRanges <- function(df) { - missingCols <- setdiff(c("chrom", "start", "end"), colnames(df)) - if (length(missingCols) > 0L) { - msg <- glue( - "`ldBlocks` table is missing required column(s): ", - "{str_flatten(missingCols, ', ')}." - ) - abort(msg) - } + assertNames( + colnames(df), + must.include = c("chrom", "start", "end"), + what = "colnames", + .var.name = "ldBlocks table" + ) gr <- GenomicRanges::GRanges( seqnames = as.character(df$chrom), ranges = IRanges::IRanges( diff --git a/R/LdData.R b/R/LdData.R index bcefd8083..e34807a4e 100644 --- a/R/LdData.R +++ b/R/LdData.R @@ -214,6 +214,8 @@ setMethod("show", "LdData", function(object) { #' blockMetadata = S4Vectors::DataFrame( #' chrom = "22", start = 1L, end = 1000L)) #' ld +#' @importFrom checkmate assert checkMatrix checkList checkNull +#' @importFrom checkmate assertNumeric #' @export LdData <- function( correlation = NULL, @@ -224,6 +226,15 @@ LdData <- function( nRef = 0L, mixtureWeights = NULL ) { + # correlation is documented as a matrix OR a list of matrices OR NULL, + # so this must be an or-combination, not assertMatrix. + assert( + checkMatrix(correlation), + checkList(correlation), + checkNull(correlation), + .var.name = "correlation" + ) + assertNumeric(mixtureWeights, null.ok = TRUE) obj <- new( "LdData", variants, diff --git a/R/LdEigen.R b/R/LdEigen.R index 5b696c68c..1edb16617 100644 --- a/R/LdEigen.R +++ b/R/LdEigen.R @@ -40,26 +40,28 @@ setClass( ) # @noRd +#' @importFrom checkmate makeAssertCollection assertList .validateLdEigen <- function(object) { + coll <- makeAssertCollection() parentCheck <- .validateLdStatistic(object) - errors <- if (isTRUE(parentCheck)) character() else parentCheck - if (length(object@eigenList) != length(object@ldBlocks)) { - errors <- c( - errors, - "Length of 'eigenList' must match number of LD blocks" - ) + if (!isTRUE(parentCheck)) { + coll$push(parentCheck) } + assertList( + object@eigenList, + len = length(object@ldBlocks), + .var.name = "eigenList", + add = coll + ) + # checkmate has no exclusive lower bound, so (0, 1] stays a plain check. if ( length(object@eigenvalueTruncation) != 1L || object@eigenvalueTruncation <= 0 || object@eigenvalueTruncation > 1 ) { - errors <- c( - errors, - "'eigenvalueTruncation' must be a single value in (0, 1]" - ) + coll$push("'eigenvalueTruncation' must be a single value in (0, 1]") } - if (length(errors) == 0) TRUE else errors + coll$getMessages() } #' @title Create an LdEigen @@ -87,6 +89,7 @@ setClass( #' ldBlocks = blocks, nRef = 100L, genome = "hg19") #' length(le) #' length(getEigenList(le)) +#' @importFrom checkmate assertDataFrame assertList assertCount #' @export LdEigen <- function( snpInfo, @@ -97,6 +100,9 @@ LdEigen <- function( genome = NA_character_, eigenvalueTruncation = 1 ) { + assertDataFrame(snpInfo) + assertList(eigenList) + assertCount(nRef, positive = TRUE) obj <- methods::new( "LdEigen", .ldStatRanges(snpInfo, genome), @@ -110,6 +116,7 @@ LdEigen <- function( obj } +#' @importFrom checkmate assertCount assertFlag #' @title Build an LdEigen from loaded LD #' @description Eigendecompose already-loaded LD, block by block, into the #' \code{LdEigen} that \code{\link{estimateH2}} consumes for @@ -160,6 +167,8 @@ buildLdEigen <- function( genome = NA_character_, eigenvalueTruncation = 1 ) { + assertCount(nRef, positive = TRUE, null.ok = TRUE) + assertFlag(inSample) prep <- .ldRefPrepare(ldBlockData, nRef, genome) eigenList <- map2( prep$blocks, diff --git a/R/LdScore.R b/R/LdScore.R index 273a36220..12bf4d613 100644 --- a/R/LdScore.R +++ b/R/LdScore.R @@ -28,16 +28,21 @@ setClass( ) # @noRd +#' @importFrom checkmate makeAssertCollection assertNames .validateLdScore <- function(object) { + coll <- makeAssertCollection() parentCheck <- .validateLdStatistic(object) - errors <- if (isTRUE(parentCheck)) character() else parentCheck - md <- S4Vectors::mcols(object, use.names = FALSE) - for (col in c("ldScores", "ldScoreWeights")) { - if (!is_in(col, colnames(md))) { - errors <- c(errors, glue("mcols must carry an '{col}' column")) - } + if (!isTRUE(parentCheck)) { + coll$push(parentCheck) } - if (length(errors) == 0) TRUE else errors + assertNames( + colnames(S4Vectors::mcols(object, use.names = FALSE)), + must.include = c("ldScores", "ldScoreWeights"), + what = "colnames", + .var.name = "mcols", + add = coll + ) + coll$getMessages() } #' @title Create an LdScore @@ -65,6 +70,7 @@ setClass( #' inSample = FALSE, genome = "hg19") #' length(ls) #' head(getLdScores(ls)) +#' @importFrom checkmate assertMatrix assertNumeric #' @export LdScore <- function( snpInfo, @@ -78,18 +84,8 @@ LdScore <- function( ) { gr <- .ldStatRanges(snpInfo, genome) ldScores <- as.matrix(ldScores) - if (nrow(ldScores) != length(gr)) { - abort(glue( - "`ldScores` has {nrow(ldScores)} row(s) for {length(gr)} ", - "variant(s); they must be parallel." - )) - } - if (length(ldScoreWeights) != length(gr)) { - abort(glue( - "`ldScoreWeights` has {length(ldScoreWeights)} value(s) for ", - "{length(gr)} variant(s); they must be parallel." - )) - } + assertMatrix(ldScores, nrows = length(gr)) + assertNumeric(ldScoreWeights, len = length(gr)) md <- S4Vectors::mcols(gr, use.names = FALSE) md$ldScores <- ldScores md$ldScoreWeights <- as.numeric(ldScoreWeights) @@ -106,6 +102,7 @@ LdScore <- function( obj } +#' @importFrom checkmate assertCount assertFlag #' @title Build an LdScore from loaded LD #' @description Compute per-variant LD scores from already-loaded LD, block by #' block, into the \code{LdScore} that \code{\link{estimateH2}} consumes @@ -162,6 +159,9 @@ buildLdScore <- function( ldScoreWeights = NULL, keepLdMatrices = TRUE ) { + assertCount(nRef, positive = TRUE, null.ok = TRUE) + assertFlag(inSample) + assertFlag(keepLdMatrices) prep <- .ldRefPrepare(ldBlockData, nRef, genome) l2 <- .ldScoreVector(prep$blocks, prep$snpIdx, nrow(prep$snpInfo)) ldMatrixList <- if (isTRUE(keepLdMatrices)) { @@ -204,12 +204,7 @@ buildLdScore <- function( if (is.null(ldScoreWeights)) { return(1 / pmax(l2, 1)) } - if (length(ldScoreWeights) != length(l2)) { - abort(glue( - "`ldScoreWeights` has {length(ldScoreWeights)} value(s) for ", - "{length(l2)} variant(s)." - )) - } + assertNumeric(ldScoreWeights, len = length(l2)) as.numeric(ldScoreWeights) } diff --git a/R/LdStatistic.R b/R/LdStatistic.R index 60d82776d..9a8e4dc5d 100644 --- a/R/LdStatistic.R +++ b/R/LdStatistic.R @@ -38,18 +38,17 @@ setClass( ) # @noRd +#' @importFrom checkmate makeAssertCollection assertCount assertLogical .validateLdStatistic <- function(object) { - errors <- character() - if (length(object@nRef) != 1L || object@nRef <= 0L) { - errors <- c(errors, "'nRef' must be a single positive integer") - } - if (length(object@inSample) != 1L) { - errors <- c(errors, "'inSample' must be a single logical value") - } + coll <- makeAssertCollection() + assertCount(object@nRef, positive = TRUE, .var.name = "nRef", add = coll) + # assertLogical(len = 1) rather than assertFlag: the slot's declared type + # already excludes non-logicals, and NA is tolerated here as it was before. + assertLogical(object@inSample, len = 1L, .var.name = "inSample", add = coll) if (length(object) == 0L) { - errors <- c(errors, "an LdStatistic must carry at least one variant") + coll$push("an LdStatistic must carry at least one variant") } - if (length(errors) == 0) TRUE else errors + coll$getMessages() } # The variants of an LD reference, as the GRanges every subclass is built on. @@ -149,14 +148,10 @@ setClass( } # @noRd +#' @importFrom checkmate assertMatrix .ldRefOneBlock <- function(R, gr) { R <- as.matrix(R) - if (nrow(R) != length(gr)) { - abort(glue( - "an LD block's correlation matrix is {nrow(R)}x{ncol(R)} but ", - "covers {length(gr)} variant(s)." - )) - } + assertMatrix(R, nrows = length(gr), .var.name = "LD block correlation") list(R = R, gr = gr) } diff --git a/R/MashPrior.R b/R/MashPrior.R index dab2ef504..0e65443d0 100644 --- a/R/MashPrior.R +++ b/R/MashPrior.R @@ -126,8 +126,10 @@ setClass( #' U <- list(shared = diag(3), singleton = matrix(0.3, 3, 3) + diag(0.7, 3)) #' mp <- MashPrior(fullFit = list(U = U, w = c(0.5, 0.5))) #' mp +#' @importFrom checkmate assertList #' @export MashPrior <- function(fullFit = NULL, cvFits = NULL) { + assertList(cvFits, null.ok = TRUE) obj <- new("MashPrior", fullFit = fullFit, cvFits = cvFits) validObject(obj) obj diff --git a/R/MultiStudyQtlDataset.R b/R/MultiStudyQtlDataset.R index 8e99792cc..af8e758d5 100644 --- a/R/MultiStudyQtlDataset.R +++ b/R/MultiStudyQtlDataset.R @@ -57,14 +57,14 @@ setClass( } # @noRd +#' @importFrom checkmate checkList .msqdCheckDatasets <- function(qtlDatasets) { - if (!is.list(qtlDatasets) || length(qtlDatasets) == 0L) { - return("'qtlDatasets' must be a non-empty named list") + # names = "unique" subsumes the old named/non-empty/non-NA/unique checks. + res <- checkList(qtlDatasets, min.len = 1L, names = "unique") + if (!isTRUE(res)) { + return(str_c("'qtlDatasets' ", res)) } - c( - .msqdCheckDatasetNames(names(qtlDatasets)), - .msqdCheckDatasetTypes(qtlDatasets) - ) + .msqdCheckDatasetTypes(qtlDatasets) } # @noRd @@ -129,7 +129,7 @@ setClass( traitRanges <- map(object@qtlDatasets, .msqdTraitRanges) pairs <- utils::combn(seq_along(traitRanges), 2L) dsNames <- names(object@qtlDatasets) - unlist(compact(map( + list_c(compact(map( seq_len(ncol(pairs)), .msqdPairErrors, pairs = pairs, @@ -231,8 +231,10 @@ setClass( #' study = "s2", genotypes = panel, phenotypes = list(brain = se) #' ) #' MultiStudyQtlDataset(qtlDatasets = list(s1 = qd1, s2 = qd2)) +#' @importFrom checkmate assertList #' @export MultiStudyQtlDataset <- function(qtlDatasets, sumStats = NULL) { + assertList(qtlDatasets, min.len = 1L, names = "unique") obj <- new( "MultiStudyQtlDataset", qtlDatasets = qtlDatasets, diff --git a/R/QtlDataset.R b/R/QtlDataset.R index 7c7bf0606..5972ed0fc 100644 --- a/R/QtlDataset.R +++ b/R/QtlDataset.R @@ -118,30 +118,39 @@ setClass( # study / scaleResiduals / keepIndel scalars + the four non-negative cutoffs. # @noRd +#' @importFrom checkmate makeAssertCollection assertString assertLogical +#' @importFrom checkmate assertFlag assertNumber +#' @importFrom purrr walk .qtlValidateScalars <- function(object) { - errors <- character() - if (length(object@study) != 1L || str_length(object@study) == 0L) { - errors <- c( - errors, - "'study' must be a single non-empty character string" - ) - } - if (length(object@scaleResiduals) != 1L) { - errors <- c(errors, "'scaleResiduals' must be a single logical value") - } - if (length(object@keepIndel) != 1L || is.na(object@keepIndel)) { - errors <- c(errors, "'keepIndel' must be a single logical value") - } - for (nm in c("mafCutoff", "macCutoff", "xvarCutoff", "imissCutoff")) { - v <- methods::slot(object, nm) - if (length(v) != 1L || is.na(v) || !is.finite(v) || v < 0) { - errors <- c( - errors, - glue("'{nm}' must be a single finite non-negative numeric") - ) - } - } - errors + coll <- makeAssertCollection() + assertString(object@study, min.chars = 1L, .var.name = "study", add = coll) + assertLogical( + object@scaleResiduals, + len = 1L, + .var.name = "scaleResiduals", + add = coll + ) + assertFlag(object@keepIndel, .var.name = "keepIndel", add = coll) + walk( + c("mafCutoff", "macCutoff", "xvarCutoff", "imissCutoff"), + .qtlValidateCutoff, + object = object, + coll = coll + ) + coll$getMessages() +} + +# Each cutoff slot must be a single finite non-negative number. +# @noRd +#' @importFrom checkmate assertNumber +.qtlValidateCutoff <- function(nm, object, coll) { + assertNumber( + methods::slot(object, nm), + lower = 0, + finite = TRUE, + .var.name = nm, + add = coll + ) } # Shape checks on the phenotype list handed to the constructor. Run before @@ -770,19 +779,17 @@ setMethod("getKeepIndel", "QtlDataset", function(x, ...) x@keepIndel) # A literal `region` GRanges, each range optionally extended by cisWindow. # @noRd +#' @importFrom checkmate assertNumber +#' @importFrom checkmate assertClass .qtlLiteralRegion <- function(region, cisWindow) { - if (!methods::is(region, "GRanges")) { - abort("`region` must be a GRanges object.") - } + assertClass(region, "GRanges") if (length(region) == 0L) { abort("`region` must contain at least one range.") } if (is.null(cisWindow)) { return(region) } - if (length(cisWindow) != 1L || cisWindow < 0) { - abort("`cisWindow` must be a single non-negative value.") - } + assertNumber(cisWindow, lower = 0) GenomicRanges::GRanges( seqnames = GenomicRanges::seqnames(region), ranges = IRanges::IRanges( @@ -841,7 +848,7 @@ setMethod("getKeepIndel", "QtlDataset", function(x, ...) x@keepIndel) #' @export setMethod("getTraitPosition", "QtlDataset", function(x, traitId = NULL, ...) { tids <- if (is.null(traitId)) { - unique(unlist(map(.qtlPhenotypeList(x), rownames))) + unique(list_c(map(.qtlPhenotypeList(x), rownames))) } else { as.character(traitId) } @@ -871,8 +878,9 @@ setMethod("getTraitPosition", "QtlDataset", function(x, traitId = NULL, ...) { # Internal: keepIndel slot read, tolerant of QtlDataset objects serialized # before the slot existed (treat a missing slot as TRUE = keep indels). +#' @importFrom purrr possibly .qtlKeepIndel <- function(x) { - isTRUE(tryCatch(getKeepIndel(x), error = function(e) TRUE)) + isTRUE(possibly(getKeepIndel, otherwise = TRUE)(x)) } # Internal: return a copy of a QtlDataset with the supplied filter cutoffs / @@ -1347,6 +1355,7 @@ setMethod( # # Returns all-TRUE (no-op) when there are too few samples to support # a covariance estimate (n < p + 2). +#' @importFrom rlang try_fetch .qtlOutlierKeepMask <- function(Y, pvalThreshold) { Y <- as.matrix(Y) n <- nrow(Y) @@ -1363,7 +1372,7 @@ setMethod( return(rep(TRUE, n)) } if (requireNamespace("robustbase", quietly = TRUE)) { - mcd <- tryCatch(robustbase::covMcd(Y), error = function(e) NULL) + mcd <- try_fetch(robustbase::covMcd(Y), error = function(cnd) NULL) if (!is.null(mcd)) { ctr <- mcd$center covMat <- mcd$cov @@ -1380,7 +1389,17 @@ setMethod( ctr <- colMeans(Y) covMat <- stats::cov(Y) } - invCov <- tryCatch(solve(covMat), error = function(e) MASS::ginv(covMat)) + invCov <- try_fetch( + solve(covMat), + error = function(cnd) { + msg <- glue( + "outlier detection: the trait covariance is singular; ", + "using a Moore-Penrose pseudo-inverse instead." + ) + inform(msg, parent = cnd) + MASS::ginv(covMat) + } + ) Yc <- sweep(Y, 2L, ctr) d2 <- rowSums((Yc %*% invCov) * Yc) raw <- stats::pchisq(d2, df = p, lower.tail = FALSE) @@ -1792,9 +1811,25 @@ setMethod( residualizeGenotypeCovariatesFromGenotypes ) || is.null(residualizeGenotypeCovariatesFromGenotypes) - p <- as.list(environment()) - p$dots <- list(...) - .qtlResidualizedGenotypesImpl(p) + .qtlResidualizedGenotypesImpl( + x = x, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + samples = samples, + phenotypeCovariatesToResidualize = phenotypeCovariatesToResidualize, + genotypeCovariatesToResidualize = genotypeCovariatesToResidualize, + covariateNaAction = covariateNaAction, + convPheno = residualizePhenotypeCovariates, + convPhenoMissing = convPhenoMissing, + precPheno = residualizePhenotypeCovariatesFromGenotypes, + precPhenoMissing = precPhenoMissing, + convGeno = residualizeGenotypeCovariates, + convGenoMissing = convGenoMissing, + precGeno = residualizeGenotypeCovariatesFromGenotypes, + precGenoMissing = precGenoMissing + ) } ) @@ -1826,66 +1861,102 @@ setMethod( # precision spellings. Paired here because the reconciliation rule is the same # for both and only the argument names differ. # @noRd -.qtlResidualizationFlags <- function(p) { +.qtlResidualizationFlags <- function( + convPheno, + convPhenoMissing, + precPheno, + precPhenoMissing, + convGeno, + convGenoMissing, + precGeno, + precGenoMissing +) { list( pheno = .qtlResolveResidualizationFlag( - p$residualizePhenotypeCovariates, - p$convPhenoMissing, - p$residualizePhenotypeCovariatesFromGenotypes, - p$precPhenoMissing, - "residualizePhenotypeCovariates", - "residualizePhenotypeCovariatesFromGenotypes" + convPheno, + convPhenoMissing, + precPheno, + precPhenoMissing, + "convPheno", + "precPheno" ), geno = .qtlResolveResidualizationFlag( - p$residualizeGenotypeCovariates, - p$convGenoMissing, - p$residualizeGenotypeCovariatesFromGenotypes, - p$precGenoMissing, - "residualizeGenotypeCovariates", - "residualizeGenotypeCovariatesFromGenotypes" + convGeno, + convGenoMissing, + precGeno, + precGenoMissing, + "convGeno", + "precGeno" ) ) } -.qtlResidualizedGenotypesImpl <- function(p) { - bad <- setdiff(p$contexts, getContexts(p$x)) +.qtlResidualizedGenotypesImpl <- function( + x, + contexts, + traitId, + region, + cisWindow, + samples, + phenotypeCovariatesToResidualize, + genotypeCovariatesToResidualize, + covariateNaAction, + convPheno, + convPhenoMissing, + precPheno, + precPhenoMissing, + convGeno, + convGenoMissing, + precGeno, + precGenoMissing +) { + bad <- setdiff(contexts, getContexts(x)) if (length(bad) > 0L) { msg <- glue("Unknown context(s): {str_flatten(bad, ', ')}") abort(msg) } - include <- .qtlResidualizationFlags(p) + include <- .qtlResidualizationFlags( + convPheno = convPheno, + convPhenoMissing = convPhenoMissing, + precPheno = precPheno, + precPhenoMissing = precPhenoMissing, + convGeno = convGeno, + convGenoMissing = convGenoMissing, + precGeno = precGeno, + precGenoMissing = precGenoMissing + ) includePheno <- include$pheno includeGeno <- include$geno phenoSel <- .qtlResolvePhenoSelection( - p$x, - p$contexts, - p$phenotypeCovariatesToResidualize + x, + contexts, + phenotypeCovariatesToResidualize ) - genoSel <- .qtlResolveGenoSelection(p$x, p$genotypeCovariatesToResidualize) + genoSel <- .qtlResolveGenoSelection(x, genotypeCovariatesToResidualize) G <- getGenotypes( - p$x, - traitId = p$traitId, - region = p$region, - cisWindow = p$cisWindow, - samples = p$samples + x, + traitId = traitId, + region = region, + cisWindow = cisWindow, + samples = samples ) if (ncol(G) == 0L) { return(G) } C <- .qtlBuildResidualizationDesign( - p$x, - contexts = p$contexts, + x, + contexts = contexts, phenoSelection = phenoSel, genoSelection = genoSel, includePheno = includePheno, includeGeno = includeGeno ) - C <- .qtlHandleCovariateNa(C, p$covariateNaAction) - aligned <- .qtlAlignGC(G, C, p$contexts) + C <- .qtlHandleCovariateNa(C, covariateNaAction) + aligned <- .qtlAlignGC(G, C, contexts) .qtlResidualizeQr( aligned$G, aligned$C, - scaleResiduals = getScaleResiduals(p$x) + scaleResiduals = getScaleResiduals(x) ) } @@ -1927,9 +1998,26 @@ setMethod( residualizeGenotypeCovariatesFromPhenotypes ) || is.null(residualizeGenotypeCovariatesFromPhenotypes) - p <- as.list(environment()) - p$dots <- list(...) - .qtlResidualizedPhenotypesImpl(p) + .qtlResidualizedPhenotypesImpl( + x = x, + contexts = contexts, + traitId = traitId, + region = region, + phenotypeCovariatesToResidualize = phenotypeCovariatesToResidualize, + genotypeCovariatesToResidualize = genotypeCovariatesToResidualize, + naAction = naAction, + covariateNaAction = covariateNaAction, + outlierAction = outlierAction, + outlierPvalThreshold = outlierPvalThreshold, + convPheno = residualizePhenotypeCovariates, + convPhenoMissing = convPhenoMissing, + precPheno = residualizePhenotypeCovariatesFromPhenotypes, + precPhenoMissing = precPhenoMissing, + convGeno = residualizeGenotypeCovariates, + convGenoMissing = convGenoMissing, + precGeno = residualizeGenotypeCovariatesFromPhenotypes, + precGenoMissing = precGenoMissing + ) } ) @@ -1989,23 +2077,32 @@ setMethod( # Resolve the phenotype/genotype covariate inclusion flags (convenience vs # precise `*FromPhenotypes`) for getResidualizedPhenotypes. # @noRd -.qtlResidPhenoFlags <- function(p) { +.qtlResidPhenoFlags <- function( + convPheno, + convPhenoMissing, + precPheno, + precPhenoMissing, + convGeno, + convGenoMissing, + precGeno, + precGenoMissing +) { list( includePheno = .qtlResolveResidualizationFlag( - p$residualizePhenotypeCovariates, - p$convPhenoMissing, - p$residualizePhenotypeCovariatesFromPhenotypes, - p$precPhenoMissing, - "residualizePhenotypeCovariates", - "residualizePhenotypeCovariatesFromPhenotypes" + convPheno, + convPhenoMissing, + precPheno, + precPhenoMissing, + "convPheno", + "precPheno" ), includeGeno = .qtlResolveResidualizationFlag( - p$residualizeGenotypeCovariates, - p$convGenoMissing, - p$residualizeGenotypeCovariatesFromPhenotypes, - p$precGenoMissing, - "residualizeGenotypeCovariates", - "residualizeGenotypeCovariatesFromPhenotypes" + convGeno, + convGenoMissing, + precGeno, + precGenoMissing, + "convGeno", + "precGeno" ) ) } @@ -2014,42 +2111,78 @@ setMethod( # NA-handle Y, build the covariate design, and per-context residualize + # outlier-filter. `p` holds the setMethod args + precomputed missing() flags. # @noRd -.qtlResidualizedPhenotypesImpl <- function(p) { - bad <- setdiff(p$contexts, getContexts(p$x)) +.qtlResidualizedPhenotypesImpl <- function( + x, + contexts, + traitId, + region, + phenotypeCovariatesToResidualize, + genotypeCovariatesToResidualize, + naAction, + covariateNaAction, + outlierAction, + outlierPvalThreshold, + convPheno, + convPhenoMissing, + precPheno, + precPhenoMissing, + convGeno, + convGenoMissing, + precGeno, + precGenoMissing +) { + bad <- setdiff(contexts, getContexts(x)) if (length(bad) > 0L) { msg <- glue("Unknown context(s): {str_flatten(bad, ', ')}") abort(msg) } - flags <- .qtlResidPhenoFlags(p) + flags <- .qtlResidPhenoFlags( + convPheno = convPheno, + convPhenoMissing = convPhenoMissing, + precPheno = precPheno, + precPhenoMissing = precPhenoMissing, + convGeno = convGeno, + convGenoMissing = convGenoMissing, + precGeno = precGeno, + precGenoMissing = precGenoMissing + ) includePheno <- flags$includePheno includeGeno <- flags$includeGeno phenoSel <- .qtlResolvePhenoSelection( - p$x, - p$contexts, - p$phenotypeCovariatesToResidualize + x, + contexts, + phenotypeCovariatesToResidualize ) - genoSel <- .qtlResolveGenoSelection(p$x, p$genotypeCovariatesToResidualize) + genoSel <- .qtlResolveGenoSelection(x, genotypeCovariatesToResidualize) Yraw <- .qtlResidPhenoY( - p$x, - p$contexts, - p$traitId, - p$region, - p$naAction + x, + contexts, + traitId, + region, + naAction ) C <- .qtlBuildResidualizationDesign( - p$x, - contexts = p$contexts, + x, + contexts = contexts, phenoSelection = phenoSel, genoSelection = genoSel, includePheno = includePheno, includeGeno = includeGeno ) - C <- .qtlHandleCovariateNa(C, p$covariateNaAction) + C <- .qtlHandleCovariateNa(C, covariateNaAction) out <- set_names( - map(p$contexts, .qtlResidualizeContext, Yraw = Yraw, C = C, p = p), - p$contexts + map( + contexts, + .qtlResidualizeContext, + Yraw = Yraw, + C = C, + x = x, + outlierAction = outlierAction, + outlierPvalThreshold = outlierPvalThreshold + ), + contexts ) - if (length(p$contexts) == 1L) out[[1L]] else out + if (length(contexts) == 1L) out[[1L]] else out } @@ -2059,10 +2192,7 @@ setMethod("show", "QtlDataset", function(object) { pheno <- .qtlPhenotypeList(object) nCtx <- length(pheno) ctxNames <- names(pheno) - totalTraits <- length(unique(unlist( - map(pheno, rownames), - use.names = FALSE - ))) + totalTraits <- length(unique(unname(list_c(map(pheno, rownames))))) cat(glue("QtlDataset for study '{object@study}'\n", .trim = FALSE)) cat(glue( " {nCtx} context(s): {str_flatten(ctxNames, ', ')}\n", @@ -2128,13 +2258,20 @@ setMethod("show", "QtlDataset", function(object) { # Residualize + filter one context's raw phenotype matrix against covariates C. # @noRd -.qtlResidualizeContext <- function(ctx, Yraw, C, p) { +.qtlResidualizeContext <- function( + ctx, + Yraw, + C, + x, + outlierAction, + outlierPvalThreshold +) { .qtlResidualizeContextPheno( Yraw[[ctx]], C, ctx, - p$outlierAction, - p$outlierPvalThreshold, - getScaleResiduals(p$x) + outlierAction, + outlierPvalThreshold, + getScaleResiduals(x) ) } diff --git a/R/QtlFineMappingResult.R b/R/QtlFineMappingResult.R index 99607609b..43dc63657 100644 --- a/R/QtlFineMappingResult.R +++ b/R/QtlFineMappingResult.R @@ -42,24 +42,23 @@ setClass( # run only once the required columns are present; the ldSketch check always # runs. # @noRd +#' @importFrom checkmate makeAssertCollection assertNames checkNames .validateQtlFineMappingResult <- function(object) { - errors <- .qfmrCheckRequiredCols(object) - if (length(errors) == 0L) { - errors <- .qfmrCheckEntries(object) - } - errors <- c(errors, .qfmrCheckLdSketch(object)) - if (length(errors) == 0L) TRUE else errors -} - -# The study/context/trait/method/entry columns must be present. -# @noRd -.qfmrCheckRequiredCols <- function(object) { - required <- c("study", "context", "trait", "method") - missingCols <- setdiff(required, .tupleColumnNames(object)) - if (length(missingCols) > 0L) { - return(str_c("missing columns: ", str_flatten(missingCols, ", "))) + coll <- makeAssertCollection() + assertNames( + .tupleColumnNames(object), + must.include = c("study", "context", "trait", "method"), + what = "colnames", + .var.name = "mcols", + add = coll + ) + # The checks below read those columns; running them on an object missing + # them reports the consequence rather than the cause. + if (!coll$isEmpty()) { + return(coll$getMessages()) } - NULL + coll$push(.qfmrCheckEntries(object)) + coll$getMessages() } # Entry-column + region/traitPos + joint-column + tuple-uniqueness contract. @@ -81,17 +80,15 @@ setClass( # to check is that the fit payload columns are present and parallel. # @noRd .qfmrCheckEntryLength <- function(object) { - missingCols <- setdiff( - c("susieFit", "cvResult"), - .tupleColumnNames(object) + res <- checkNames( + .tupleColumnNames(object), + must.include = c("susieFit", "cvResult"), + what = "colnames" ) - if (length(missingCols) > 0L) { - return(str_c( - "missing entry payload columns: ", - str_flatten(missingCols, ", ") - )) + if (isTRUE(res)) { + return(NULL) } - NULL + str_c("missing entry payload columns: ", res) } # @noRd @@ -101,7 +98,7 @@ setClass( # Each present joint* column must be character. # @noRd .qfmrCheckJointCols <- function(object, jointCols) { - unlist(compact(map(jointCols, .qfmrJointColError, object = object))) + list_c(compact(map(jointCols, .qfmrJointColError, object = object))) } # @noRd @@ -141,12 +138,6 @@ setClass( } # ldSketch must be a GenotypeHandle or NULL. -# @noRd -.qfmrCheckLdSketch <- function(object) { - # The slot's class union enforces the type; nothing to check. - NULL -} - # The identity-tuple vectors and the payload list are parallel: one entry per # (study, context, trait, method). A length mismatch would otherwise surface as @@ -204,6 +195,8 @@ setClass( #' variantIds = tl$variant_id, susieFit = list(), topLoci = tl) #' QtlFineMappingResult(study = "s1", context = "brain", trait = "g1", #' method = "susie", entry = list(fe)) +#' @importFrom checkmate assertCharacter assert checkList +#' @importFrom checkmate checkClass #' @export QtlFineMappingResult <- function( study, @@ -217,6 +210,20 @@ QtlFineMappingResult <- function( traitPos = NULL, ldSketch = NULL ) { + assertCharacter(study, any.missing = FALSE) + assertCharacter(context, any.missing = FALSE) + assertCharacter(trait, any.missing = FALSE) + assertCharacter(method, any.missing = FALSE) + # `entry` is documented as "List / SimpleList"; SimpleList is S4 and + # fails checkList, so this must be an or-combination. + assert( + checkList(entry), + checkClass(entry, "SimpleList"), + .var.name = "entry" + ) + assertCharacter(jointStudies, null.ok = TRUE) + assertCharacter(jointContexts, null.ok = TRUE) + assertCharacter(jointTraits, null.ok = TRUE) n <- length(study) .qfmrCheckTupleLengths(study, context, trait, method, entry) entry <- map(entry, .asFmRowPayload) diff --git a/R/RangedTupleList.R b/R/RangedTupleList.R index 8024da1b1..9c5a22b34 100644 --- a/R/RangedTupleList.R +++ b/R/RangedTupleList.R @@ -324,6 +324,7 @@ setMethod("[[<-", "RangedTupleList", function(x, i, j, ..., value) { # Resolve `[[` index forms (positive integer or element name) to a position. # @noRd +#' @importFrom checkmate assertScalar .rtlAssignIndex <- function(x, i) { if (is.character(i)) { pos <- match(i, names(x)) @@ -332,9 +333,7 @@ setMethod("[[<-", "RangedTupleList", function(x, i, j, ..., value) { } return(pos) } - if (length(i) != 1L || is.na(i)) { - abort("`[[<-` takes a single non-NA index.") - } + assertScalar(i, na.ok = FALSE, .var.name = "`[[<-` index") as.integer(i) } @@ -436,7 +435,12 @@ setMethod("subsetRegion", "RangedTupleList", function(x, region, ...) { } pieces <- map(entry, .rtlSplitOne) list( - entry = unlist(pieces, recursive = FALSE, use.names = TRUE), + # unname(): the flattened names were an artifact of base unlist()'s + # `outer.inner` mangling and were never coherent -- a multi-seqname + # entry got `a.chr1`, a single-seqname one got bare `a`, an unnamed + # input got `chr1` or "". Nothing reads them, and the seqname path + # already had to unname() derived values to keep them out of mcols. + entry = unname(list_flatten(pieces)), fromIdx = rep(seq_along(entry), lengths(pieces)) ) } @@ -499,7 +503,12 @@ setMethod("subsetRegion", "RangedTupleList", function(x, region, ...) { warn(msg) } list( - entry = unlist(pieces, recursive = FALSE, use.names = TRUE), + # unname(): the flattened names were an artifact of base unlist()'s + # `outer.inner` mangling and were never coherent -- a multi-seqname + # entry got `a.chr1`, a single-seqname one got bare `a`, an unnamed + # input got `chr1` or "". Nothing reads them, and the seqname path + # already had to unname() derived values to keep them out of mcols. + entry = unname(list_flatten(pieces)), fromIdx = rep(seq_along(entry), lengths(pieces)), blockId = unname(list_c(map(pieces, names))) ) diff --git a/R/SldscData.R b/R/SldscData.R index 65a87abff..9604d72e2 100644 --- a/R/SldscData.R +++ b/R/SldscData.R @@ -46,10 +46,12 @@ setValidity("SldscData", function(object) .validateSldscData(object)) } # @noRd +#' @importFrom checkmate checkNames .sldscDataCheckAnnot <- function(annot) { errs <- character(0) - if (!all(is_in(c("CHR", "SNP"), names(annot)))) { - errs <- c(errs, "`annot` must have columns CHR and SNP.") + cols <- checkNames(names(annot), must.include = c("CHR", "SNP")) + if (!isTRUE(cols)) { + errs <- c(errs, str_c("`annot` must have columns CHR and SNP: ", cols)) } annotCols <- setdiff(names(annot), c("CHR", "SNP", "BP", "CM")) if (length(annotCols) == 0L) { @@ -66,10 +68,14 @@ setValidity("SldscData", function(object) .validateSldscData(object)) # @noRd .sldscDataCheckFrq <- function(frq) { - if (nrow(frq) > 0L && !all(is_in(c("SNP", "MAF"), names(frq)))) { - return("non-empty `frq` must have columns SNP and MAF.") + if (nrow(frq) == 0L) { + return(NULL) } - NULL + cols <- checkNames(names(frq), must.include = c("SNP", "MAF")) + if (isTRUE(cols)) { + return(NULL) + } + str_c("non-empty `frq` must have columns SNP and MAF: ", cols) } # @noRd @@ -81,7 +87,7 @@ setValidity("SldscData", function(object) .validateSldscData(object)) if (is.null(names(tr)) || any(str_length(names(tr)) == 0L, na.rm = TRUE)) { errs <- c(errs, "`traits` must be a named list (one entry per trait).") } - c(errs, unlist(compact(map(names(tr), .sldscDataCheckOneTrait, tr = tr)))) + c(errs, list_c(compact(map(names(tr), .sldscDataCheckOneTrait, tr = tr)))) } # @noRd diff --git a/R/causalInferencePipeline.R b/R/causalInferencePipeline.R index c15228ca5..9f235ea60 100644 --- a/R/causalInferencePipeline.R +++ b/R/causalInferencePipeline.R @@ -102,7 +102,6 @@ #' to the GWAS by (chrom, pos) with ref/alt swaps recognized and the exposure #' effect / weight sign-flipped accordingly; when FALSE, match on exact #' alleles only, so a ref/alt swap is treated as a distinct variant. -#' @param ... Reserved. #' @return A \code{GRanges} as described above. #' @examples #' data(qtlDatasetExample) @@ -125,48 +124,87 @@ causalInferencePipeline <- function( mrCpipCutoff = 0.5, mrPvalCutoff = 1, combineMethods = NULL, - alleleFlip = TRUE, - ... + alleleFlip = TRUE ) { mrMethod <- arg_match(mrMethod) - p <- as.list(environment()) - p$dots <- list(...) - .cipRun(p) + .cipRun( + gwasSumStats = gwasSumStats, + twasWeights = twasWeights, + fineMappingResult = fineMappingResult, + combineMethods = combineMethods, + rsqCutoff = rsqCutoff, + rsqOption = rsqOption, + rsqPvalCutoff = rsqPvalCutoff, + rsqPvalOption = rsqPvalOption, + alleleFlip = alleleFlip, + mrMethod = mrMethod, + mrPipCutoff = mrPipCutoff, + mrCpipCutoff = mrCpipCutoff, + mrPvalCutoff = mrPvalCutoff + ) } -# Orchestrate the causal-inference pipeline over a parameter bundle `p` (from -# as.list(environment())): validate, resolve the QTL work list, apply optional -# CV selection, score every (qtl, gwas) pair, and finalize. +# Orchestrate the causal-inference pipeline: validate, resolve the QTL work +# list, apply optional CV selection, score every (qtl, gwas) pair, finalize. # @noRd -.cipRun <- function(p) { - .cipValidateInputs(p$gwasSumStats, p$twasWeights, p$fineMappingResult) - p$gwasLd <- .cipCheckLdSketches( - p$gwasSumStats, - p$twasWeights, - p$fineMappingResult +.cipRun <- function( + gwasSumStats, + twasWeights, + fineMappingResult, + combineMethods, + rsqCutoff, + rsqOption, + rsqPvalCutoff, + rsqPvalOption, + alleleFlip, + mrMethod, + mrPipCutoff, + mrCpipCutoff, + mrPvalCutoff +) { + .cipValidateInputs(gwasSumStats, twasWeights, fineMappingResult) + gwasLd <- .cipCheckLdSketches( + gwasSumStats, + twasWeights, + fineMappingResult + ) + qtlRows <- .cipResolveWorkList(twasWeights, fineMappingResult) + sel <- .cipCvSelection( + qtlRows = qtlRows, + twasWeights = twasWeights, + rsqCutoff = rsqCutoff, + rsqOption = rsqOption, + rsqPvalCutoff = rsqPvalCutoff, + rsqPvalOption = rsqPvalOption ) - p$qtlRows <- .cipResolveWorkList(p$twasWeights, p$fineMappingResult) - sel <- .cipCvSelection(p) - p$qtlRows <- sel$qtlRows + qtlRows <- sel$qtlRows outRows <- list_flatten(map( - seq_len(nrow(p$qtlRows)), + seq_len(nrow(qtlRows)), .cipScoreQtlTuple, - p = p + qtlRows = qtlRows, + twasWeights = twasWeights, + fineMappingResult = fineMappingResult, + gwasSumStats = gwasSumStats, + gwasLd = gwasLd, + alleleFlip = alleleFlip, + mrMethod = mrMethod, + mrPipCutoff = mrPipCutoff, + mrCpipCutoff = mrCpipCutoff, + mrPvalCutoff = mrPvalCutoff )) if (length(outRows) == 0L) { abort( "causalInferencePipeline: no (qtl, gwas) tuples produced a result." ) } - .cipFinalize(outRows, sel, p$combineMethods) + .cipFinalize(outRows, sel, combineMethods) } # Validate the object classes + QC state of the pipeline inputs. # @noRd +#' @importFrom checkmate assertClass checkClass .cipValidateInputs <- function(gwasSumStats, twasWeights, fineMappingResult) { - if (!methods::is(gwasSumStats, "GwasSumStats")) { - abort("`gwasSumStats` must be a GwasSumStats object.") - } + assertClass(gwasSumStats, "GwasSumStats") if (length(getQcInfo(gwasSumStats)) == 0L) { msg <- glue( "causalInferencePipeline: gwasSumStats has no QC record ", @@ -181,17 +219,18 @@ causalInferencePipeline <- function( ) abort(msg) } - if (!is.null(twasWeights) && !methods::is(twasWeights, "TwasWeights")) { - abort("`twasWeights` must be a TwasWeights object or NULL.") - } - if ( - !is.null(fineMappingResult) && - !methods::is(fineMappingResult, "QtlFineMappingResult") - ) { + assertClass(twasWeights, "TwasWeights", null.ok = TRUE) + # Composed rather than asserted: rejecting the GWAS-side class here is + # deliberate, and checkClass alone cannot say so. + res <- checkClass( + fineMappingResult, + "QtlFineMappingResult", + null.ok = TRUE + ) + if (!isTRUE(res)) { msg <- glue( - "`fineMappingResult` must be a QtlFineMappingResult or NULL ", - "(causalInferencePipeline does not accept GWAS-side fine ", - "mapping for the QTL slot)." + "`fineMappingResult` {res} (causalInferencePipeline does not ", + "accept GWAS-side fine mapping for the QTL slot)." ) abort(msg) } @@ -240,21 +279,28 @@ causalInferencePipeline <- function( # filter to eligible methods now, deferring the final best-method pick to after # the TWAS Z. Returns list(qtlRows, rsqLookup, selectionActive). # @noRd -.cipCvSelection <- function(p) { - selectionActive <- !is.null(p$twasWeights) && - (p$rsqCutoff > 0 || is.finite(p$rsqPvalCutoff)) +.cipCvSelection <- function( + qtlRows, + twasWeights, + rsqCutoff, + rsqOption, + rsqPvalCutoff, + rsqPvalOption +) { + selectionActive <- !is.null(twasWeights) && + (rsqCutoff > 0 || is.finite(rsqPvalCutoff)) if (!selectionActive) { return(list( - qtlRows = p$qtlRows, + qtlRows = qtlRows, rsqLookup = NULL, selectionActive = FALSE )) } metricTab <- .cipMethodMetrics( - p$qtlRows, - p$twasWeights, - p$rsqOption, - p$rsqPvalOption + qtlRows, + twasWeights, + rsqOption, + rsqPvalOption ) rsqLookup <- set_names( metricTab$rsq, @@ -267,15 +313,15 @@ causalInferencePipeline <- function( ) ) qtlRows <- .cipFilterEligibleMethods( - p$qtlRows, + qtlRows, metricTab, - p$rsqCutoff, - p$rsqPvalCutoff + rsqCutoff, + rsqPvalCutoff ) if (nrow(qtlRows) == 0L) { msg <- glue( "causalInferencePipeline: every QTL tuple was filtered out by ", - "rsqCutoff = {p$rsqCutoff} / rsqPvalCutoff = {p$rsqPvalCutoff} ", + "rsqCutoff = {rsqCutoff} / rsqPvalCutoff = {rsqPvalCutoff} ", "(no method cleared the CV cutoffs)." ) abort(msg) @@ -286,25 +332,37 @@ causalInferencePipeline <- function( # Score one QTL tuple against every GWAS study -> a list of result records # (empty when the tuple has no usable weights). # @noRd -.cipScoreQtlTuple <- function(qi, p) { - qStudy <- p$qtlRows$qtlStudy[[qi]] - qContext <- p$qtlRows$context[[qi]] - qTrait <- p$qtlRows$trait[[qi]] - qMethod <- p$qtlRows$method[[qi]] +.cipScoreQtlTuple <- function( + qi, + qtlRows, + twasWeights, + fineMappingResult, + gwasSumStats, + gwasLd, + alleleFlip, + mrMethod, + mrPipCutoff, + mrCpipCutoff, + mrPvalCutoff +) { + qStudy <- qtlRows$qtlStudy[[qi]] + qContext <- qtlRows$context[[qi]] + qTrait <- qtlRows$trait[[qi]] + qMethod <- qtlRows$method[[qi]] weightsInfo <- .cipExtractWeights( - twasWeights = p$twasWeights, - fineMappingResult = p$fineMappingResult, + twasWeights = twasWeights, + fineMappingResult = fineMappingResult, study = qStudy, context = qContext, trait = qTrait, method = qMethod, - useFmr = p$qtlRows$useFmrForWeights[[qi]] + useFmr = qtlRows$useFmrForWeights[[qi]] ) if (is.null(weightsInfo)) { return(list()) } fmrEntry <- .cipResolveFmrEntry( - p$fineMappingResult, + fineMappingResult, qStudy, qContext, qTrait, @@ -317,12 +375,18 @@ causalInferencePipeline <- function( qMethod = qMethod ) compact(map( - seq_len(nrow(p$gwasSumStats)), + seq_len(nrow(gwasSumStats)), .cipScoreGwasPair, tuple = tuple, weightsInfo = weightsInfo, fmrEntry = fmrEntry, - p = p + gwasSumStats = gwasSumStats, + gwasLd = gwasLd, + alleleFlip = alleleFlip, + mrMethod = mrMethod, + mrPipCutoff = mrPipCutoff, + mrCpipCutoff = mrCpipCutoff, + mrPvalCutoff = mrPvalCutoff )) } @@ -352,10 +416,22 @@ causalInferencePipeline <- function( # Score one (qtl tuple, gwas study) pair -> a result record, or NULL when the # TWAS Z cannot be computed (too little overlap). # @noRd -.cipScoreGwasPair <- function(gi, tuple, weightsInfo, fmrEntry, p) { - gStudy <- as.character(p$gwasSumStats$study)[[gi]] +.cipScoreGwasPair <- function( + gi, + tuple, + weightsInfo, + fmrEntry, + gwasSumStats, + gwasLd, + alleleFlip, + mrMethod, + mrPipCutoff, + mrCpipCutoff, + mrPvalCutoff +) { + gStudy <- as.character(gwasSumStats$study)[[gi]] gdf <- getSumStatsDf( - p$gwasSumStats, + gwasSumStats, study = gStudy, require = c("SNP", "Z") ) @@ -363,14 +439,23 @@ causalInferencePipeline <- function( weights = weightsInfo$weights, variantIds = weightsInfo$variantIds, gwasDf = gdf, - gwasLd = p$gwasLd, - alleleFlip = p$alleleFlip, + gwasLd = gwasLd, + alleleFlip = alleleFlip, label = .cipPairLabel(tuple, gStudy) ) if (is.null(twasOut)) { return(NULL) } - mrOut <- .cipRunMr(fmrEntry, gdf, twasOut, p) + mrOut <- .cipRunMr( + fmrEntry, + gdf, + twasOut, + alleleFlip = alleleFlip, + mrMethod = mrMethod, + mrPipCutoff = mrPipCutoff, + mrCpipCutoff = mrCpipCutoff, + mrPvalCutoff = mrPvalCutoff + ) .cipResultRow(tuple, gStudy, twasOut, mrOut) } @@ -387,25 +472,34 @@ causalInferencePipeline <- function( # Run MR for a pair, gated on the TWAS p-value (mrPvalCutoff >= 1 disables the # gate) and the presence of a fine-mapping entry. # @noRd -.cipRunMr <- function(fmrEntry, gdf, twasOut, p) { - mrGateOpen <- p$mrPvalCutoff >= 1 || - (!is.na(twasOut$pval) && twasOut$pval < p$mrPvalCutoff) +.cipRunMr <- function( + fmrEntry, + gdf, + twasOut, + alleleFlip, + mrMethod, + mrPipCutoff, + mrCpipCutoff, + mrPvalCutoff +) { + mrGateOpen <- mrPvalCutoff >= 1 || + (!is.na(twasOut$pval) && twasOut$pval < mrPvalCutoff) if (is.null(fmrEntry) || !mrGateOpen) { return(.cipEmptyMr()) } - if (p$mrMethod == "csAware") { + if (mrMethod == "csAware") { .cipComputeMrCsAware( fmrEntry = fmrEntry, gwasDf = gdf, - cpipCutoff = p$mrCpipCutoff, - alleleFlip = p$alleleFlip + cpipCutoff = mrCpipCutoff, + alleleFlip = alleleFlip ) } else { .cipComputeMr( fmrEntry = fmrEntry, gwasDf = gdf, - pipCutoff = p$mrPipCutoff, - alleleFlip = p$alleleFlip + pipCutoff = mrPipCutoff, + alleleFlip = alleleFlip ) } } @@ -511,8 +605,9 @@ causalInferencePipeline <- function( # metrics vector / data frame is tolerated too. `which` is a vector of candidate # metric names; the first present is used. Returns NA when no usable metric. # @noRd +#' @importFrom rlang try_fetch .cipCvMetric <- function(twasWeights, study, context, trait, method, which) { - perf <- tryCatch( + perf <- try_fetch( getCvResult( twasWeights, study = study, @@ -520,7 +615,7 @@ causalInferencePipeline <- function( trait = trait, method = method ), - error = function(e) NULL + error = function(cnd) NULL ) if (is.null(perf)) { return(NA_real_) @@ -1235,6 +1330,7 @@ causalInferencePipeline <- function( # Row-align V to the weights (by name when available, else positionally). # @noRd +#' @importFrom checkmate assertMatrix .twasZAlignV <- function(V, rn, nW) { if (!is.null(rownames(V)) && !is.null(rn)) { idx <- match(rn, rownames(V)) @@ -1247,11 +1343,7 @@ causalInferencePipeline <- function( } return(V[idx, , drop = FALSE]) } - if (nrow(V) != nW) { - abort( - "twasZ: positional alignment requires nrow(V) == nrow(weights)." - ) - } + assertMatrix(V, nrows = nW, .var.name = "V (positional alignment)") V } @@ -1270,6 +1362,7 @@ causalInferencePipeline <- function( # Symmetric-align R to the weights (by name when available, else positionally). # @noRd +#' @importFrom checkmate assertMatrix .twasZAlignR <- function(R, rn, nW) { if (!is.null(rownames(R)) && !is.null(rn)) { idx <- match(rn, rownames(R)) @@ -1282,11 +1375,7 @@ causalInferencePipeline <- function( } return(R[idx, idx, drop = FALSE]) } - if (nrow(R) != nW) { - abort( - "twasZ: positional alignment requires nrow(R) == nrow(weights)." - ) - } + assertMatrix(R, nrows = nW, .var.name = "R (positional alignment)") R } @@ -1347,6 +1436,7 @@ causalInferencePipeline <- function( #' w <- setNames(rnorm(20) * 0.1, colnames(X)) #' twasZ(weights = w, z = rnorm(20), R = cor(X)) #' @export +#' @importFrom checkmate assertCharacter assertCount twasZ <- function( weights, z, @@ -1357,6 +1447,8 @@ twasZ <- function( nSketch = NULL, combineMethods = NULL ) { + assertCount(nSketch, positive = TRUE, null.ok = TRUE) + assertCharacter(combineMethods, null.ok = TRUE) weights <- .twasZPrepWeights(weights, z) covY <- .twasZCovY( weights = weights, @@ -1387,6 +1479,7 @@ twasZ <- function( # Coerce a weight vector to a one-column matrix, validate the class / dims, and # default the column names. # @noRd +#' @importFrom checkmate assertMatrix .twasZPrepWeights <- function(weights, z) { if (is.numeric(weights) && is.null(dim(weights))) { nm <- if (!is.null(names(weights))) names(weights) else NULL @@ -1398,9 +1491,7 @@ twasZ <- function( if (is.null(colnames(weights))) { colnames(weights) <- str_c("method", seq_len(ncol(weights))) } - if (nrow(weights) != length(z)) { - abort("nrow(weights) must equal length(z).") - } + assertMatrix(weights, nrows = length(z)) weights } diff --git a/R/colocPipeline.R b/R/colocPipeline.R index 8a21f9ec8..f4b6bb061 100644 --- a/R/colocPipeline.R +++ b/R/colocPipeline.R @@ -130,7 +130,8 @@ #' between the QTL and GWAS by (chrom, pos) with ref/alt swaps recognized (LBF #' is coding-invariant, so no sign change is needed); when FALSE, match on #' exact alleles only, so a ref/alt swap is treated as a distinct variant. -#' @param ... Additional arguments forwarded to \code{coloc::coloc.bf_bf}. +#' @param colocArgs Optional named list of additional arguments forwarded +#' to \code{coloc::coloc.bf_bf}. #' @return A \code{\linkS4class{ColocResult}}: one element per tested #' (first-side credible set, second-side credible set, block) pair, holding #' that pair's aligned variants with their \code{SNP.PP.H4}. Pair-level @@ -178,70 +179,106 @@ colocPipeline <- function( p12Max = 1e-3, adjustPips = TRUE, alleleFlip = TRUE, - ... + colocArgs = list() ) { - p <- as.list(environment()) - p$dots <- list(...) - p$useEnrichment <- !is.null(enrichment) - .colocValidateInputs(p) - p$gwasFmr <- .colocResolveGwasFmr(gwasInput, finemappingMethods) + useEnrichment <- !is.null(enrichment) + .colocValidateInputs( + gwasInput = gwasInput, + qtlFineMappingResult = qtlFineMappingResult, + enrichment = enrichment, + useEnrichment = useEnrichment + ) + gwasFmr <- .colocResolveGwasFmr(gwasInput, finemappingMethods) .colocRequireMatchingLdSketches( getLdSketch(qtlFineMappingResult), - getLdSketch(p$gwasFmr) + getLdSketch(gwasFmr) ) - p <- .colocMaybeAdjustPips(p) + adjusted <- .colocMaybeAdjustPips( + adjustPips = adjustPips, + qtlFineMappingResult = qtlFineMappingResult, + gwasFmr = gwasFmr + ) + qtlFineMappingResult <- adjusted$qtlFineMappingResult + gwasFmr <- adjusted$gwasFmr # Pre-extract per-GWAS-tuple LBF matrices: group the GWAS FMR by study, # stack each study's LBF rows, and store per-(study, method) batched # matrices (reproduces the legacy row-wise combine per xQTL). - p$gwasLbfByPair <- .colocPreextractGwasLbf( - p$gwasFmr, + gwasLbfByPair <- .colocPreextractGwasLbf( + gwasFmr, filterLbfCs, filterLbfCsSecondary, filterLbfCsConcentration, priorTol ) - if (length(p$gwasLbfByPair) == 0L) { - return(.colocEarlyReturn(p)) + if (length(gwasLbfByPair) == 0L) { + return(.colocEarlyReturn( + useEnrichment = useEnrichment, + qtlFineMappingResult = qtlFineMappingResult, + gwasFmr = gwasFmr, + gwasInput = gwasInput, + returnGwasFineMapping = returnGwasFineMapping + )) } results <- list_flatten(map( - seq_len(nrow(p$qtlFineMappingResult)), + seq_len(nrow(qtlFineMappingResult)), .colocScoreQtlTuple, - p = p + qtlFineMappingResult = qtlFineMappingResult, + gwasLbfByPair = gwasLbfByPair, + filterLbfCs = filterLbfCs, + filterLbfCsSecondary = filterLbfCsSecondary, + filterLbfCsConcentration = filterLbfCsConcentration, + priorTol = priorTol, + useEnrichment = useEnrichment, + enrichment = enrichment, + p12 = p12, + p12Max = p12Max, + p1 = p1, + p2 = p2, + alleleFlip = alleleFlip, + colocArgs = colocArgs )) - .colocFinalize(results, p) + .colocFinalize( + results, + useEnrichment = useEnrichment, + qtlFineMappingResult = qtlFineMappingResult, + gwasFmr = gwasFmr, + gwasInput = gwasInput, + returnGwasFineMapping = returnGwasFineMapping + ) } # Validate the enrichment table (when supplied), the coloc package, and the # input object classes. # @noRd -.colocValidateInputs <- function(p) { - if (p$useEnrichment) { - .colocValidateEnrichment(p$enrichment) +.colocValidateInputs <- function( + gwasInput, + qtlFineMappingResult, + enrichment, + useEnrichment +) { + if (useEnrichment) { + .colocValidateEnrichment(enrichment) } if (!requireNamespace("coloc", quietly = TRUE)) { - msg <- glue( - "Package 'coloc' is required for colocPipeline. ", - "Install with: install.packages('coloc')." - ) - abort(msg) + abort("Package 'coloc' is required for colocPipeline.") } - if (!methods::is(p$qtlFineMappingResult, "FineMappingResultBase")) { + if (!methods::is(qtlFineMappingResult, "FineMappingResultBase")) { msg <- glue( "`qtlFineMappingResult` must be a QtlFineMappingResult or a ", "GwasFineMappingResult ", - "(got class '{class(p$qtlFineMappingResult)[[1L]]}')." + "(got class '{class(qtlFineMappingResult)[[1L]]}')." ) abort(msg) } if ( - !methods::is(p$gwasInput, "SumStatsBase") && - !methods::is(p$gwasInput, "FineMappingResultBase") + !methods::is(gwasInput, "SumStatsBase") && + !methods::is(gwasInput, "FineMappingResultBase") ) { msg <- glue( "`gwasInput` must be a fine-mapping result ", "(QtlFineMappingResult / GwasFineMappingResult) or summary ", "statistics (QtlSumStats / GwasSumStats) ", - "(got class '{class(p$gwasInput)[[1L]]}')." + "(got class '{class(gwasInput)[[1L]]}')." ) abort(msg) } @@ -251,24 +288,18 @@ colocPipeline <- function( # The enrichment table must be a data.frame carrying the required id + value # columns. # @noRd +#' @importFrom checkmate assertDataFrame assertNames .colocValidateEnrichment <- function(enrichment) { - if (!is.data.frame(enrichment)) { - msg <- glue( - "`enrichment` must be a data.frame with at least gwasStudy, ", - "qtlStudy, qtlContext, enrichment columns (output of ", - "qtlEnrichmentPipeline)." - ) - abort(msg) - } - required <- c("gwasStudy", "qtlStudy", "qtlContext", "enrichment") - missingCols <- setdiff(required, colnames(enrichment)) - if (length(missingCols) > 0L) { - msg <- glue( - "`enrichment` is missing column(s): ", - "{str_flatten(missingCols, ', ')}" - ) - abort(msg) - } + # The producer is named in .var.name so a caller passing the wrong object + # is pointed at what produces the right one. + label <- "enrichment (output of qtlEnrichmentPipeline)" + assertDataFrame(enrichment, .var.name = label) + assertNames( + colnames(enrichment), + must.include = c("gwasStudy", "qtlStudy", "qtlContext", "enrichment"), + what = "colnames", + .var.name = label + ) .colocValidateEnrichmentKeys(enrichment) invisible(NULL) } @@ -318,17 +349,23 @@ colocPipeline <- function( # adjusted each side to the UNION of the other's variants -- a union is not an # intersection, so the two sides could still end up on different sets. # @noRd -.colocMaybeAdjustPips <- function(p) { - if (!isTRUE(p$adjustPips)) { - return(p) +.colocMaybeAdjustPips <- function( + adjustPips, + qtlFineMappingResult, + gwasFmr +) { + unchanged <- list( + qtlFineMappingResult = qtlFineMappingResult, + gwasFmr = gwasFmr + ) + if (!isTRUE(adjustPips)) { + return(unchanged) } - if (nrow(p$qtlFineMappingResult) == 0L || nrow(p$gwasFmr) == 0L) { - return(p) + if (nrow(qtlFineMappingResult) == 0L || nrow(gwasFmr) == 0L) { + return(unchanged) } - both <- intersectVariants(p$qtlFineMappingResult, p$gwasFmr) - p$qtlFineMappingResult <- both$x - p$gwasFmr <- both$y - p + both <- intersectVariants(qtlFineMappingResult, gwasFmr) + list(qtlFineMappingResult = both$x, gwasFmr = both$y) } # The LD reference the result carries forward, so getColocCredibleSets() can @@ -338,33 +375,55 @@ colocPipeline <- function( # first side fit on individual-level data carries none, and then the second # side's panel is the only one there is. # @noRd -.colocLdSketch <- function(p) { - getLdSketch(p$qtlFineMappingResult) %||% getLdSketch(p$gwasFmr) +.colocLdSketch <- function(qtlFineMappingResult, gwasFmr) { + getLdSketch(qtlFineMappingResult) %||% getLdSketch(gwasFmr) } # Empty-result early return (attaching the GWAS fine-mapping when requested). # @noRd -.colocEarlyReturn <- function(p) { +.colocEarlyReturn <- function( + useEnrichment, + qtlFineMappingResult, + gwasFmr, + gwasInput, + returnGwasFineMapping +) { out <- .colocEmptyResult( - enriched = p$useEnrichment, - ldSketch = .colocLdSketch(p) + enriched = useEnrichment, + ldSketch = .colocLdSketch(qtlFineMappingResult, gwasFmr) ) - if (p$returnGwasFineMapping && methods::is(p$gwasInput, "SumStatsBase")) { - attr(out, "gwasFineMapping") <- p$gwasFmr + if (returnGwasFineMapping && methods::is(gwasInput, "SumStatsBase")) { + attr(out, "gwasFineMapping") <- gwasFmr } out } # Score one QTL tuple against every pre-extracted GWAS pair -> summary rows. # @noRd -.colocScoreQtlTuple <- function(qi, p) { - q <- .colocQtlTupleInfo(qi, p) +.colocScoreQtlTuple <- function( + qi, + qtlFineMappingResult, + gwasLbfByPair, + filterLbfCs, + filterLbfCsSecondary, + filterLbfCsConcentration, + priorTol, + useEnrichment, + enrichment, + p12, + p12Max, + p1, + p2, + alleleFlip, + colocArgs +) { + q <- .colocQtlTupleInfo(qi, qtlFineMappingResult) qLbfInfo <- .colocExtractLbfFromEntry( q$parts, - p$filterLbfCs, - p$filterLbfCsSecondary, - p$filterLbfCsConcentration, - p$priorTol, + filterLbfCs, + filterLbfCsSecondary, + filterLbfCsConcentration, + priorTol, label = q$label ) if (is.null(qLbfInfo)) { @@ -373,18 +432,25 @@ colocPipeline <- function( q$retainedMass <- qLbfInfo$retainedMass q$effect <- qLbfInfo$effect compact(map( - p$gwasLbfByPair, + gwasLbfByPair, .colocScorePairAt, qLbfInfo = qLbfInfo, - p = p, - q = q + q = q, + useEnrichment = useEnrichment, + enrichment = enrichment, + p12 = p12, + p12Max = p12Max, + p1 = p1, + p2 = p2, + alleleFlip = alleleFlip, + colocArgs = colocArgs )) } # Identity + row payload + log label for one first-side tuple. # @noRd -.colocQtlTupleInfo <- function(qi, p) { - fmr <- p$qtlFineMappingResult +.colocQtlTupleInfo <- function(qi, qtlFineMappingResult) { + fmr <- qtlFineMappingResult ident <- .colocTupleIdentity(fmr, qi) c( ident, @@ -410,19 +476,46 @@ colocPipeline <- function( # Score one (QTL, GWAS) pair via coloc.bf_bf -> a summary row, or NULL when the # variants don't align or coloc fails / returns no summary. # @noRd -.colocScorePair <- function(qLbf, gInfo, q, p) { +.colocScorePair <- function( + qLbf, + gInfo, + q, + useEnrichment, + enrichment, + p12, + p12Max, + p1, + p2, + alleleFlip, + colocArgs +) { # Align variants between the QTL and GWAS LBF matrices by (chrom, pos, # allele) tuple via matchVariants (see .colocAlignLbf). - aligned <- .colocAlignLbf(qLbf, gInfo$lbf, alleleFlip = p$alleleFlip) + aligned <- .colocAlignLbf(qLbf, gInfo$lbf, alleleFlip = alleleFlip) if (is.null(aligned)) { return(NULL) } - p12Info <- .colocResolveP12(p, gInfo, q) - pairRes <- .colocRunPair(aligned, p, p12Info$p12Used, q, gInfo) + p12Info <- .colocResolveP12( + gInfo, + q, + useEnrichment = useEnrichment, + enrichment = enrichment, + p12 = p12, + p12Max = p12Max + ) + pairRes <- .colocRunPair( + aligned, + p12Info$p12Used, + q, + gInfo, + p1 = p1, + p2 = p2, + colocArgs = colocArgs + ) if (is.null(pairRes) || is.null(pairRes$summary)) { return(NULL) } - rows <- .colocSummaryRow(pairRes, q, gInfo, p, p12Info) + rows <- .colocSummaryRow(pairRes, q, gInfo, useEnrichment, p12Info) # $results is the per-variant layer that process_coloc_results() used to # consume and that this pipeline silently dropped. It is pivoted here, the # only place that knows which results column belongs to which summary row. @@ -435,11 +528,18 @@ colocPipeline <- function( # Enrichment-informed p12 (per-(gwasStudy, qtlStudy, qtlContext) scaling capped # at p12Max; baseline p12 with no enrichment table / no matching row). # @noRd -.colocResolveP12 <- function(p, gInfo, q) { - if (!p$useEnrichment) { - return(list(enRow = NA_real_, p12Used = p$p12)) +.colocResolveP12 <- function( + gInfo, + q, + useEnrichment, + enrichment, + p12, + p12Max +) { + if (!useEnrichment) { + return(list(enRow = NA_real_, p12Used = p12)) } - enRow <- .colocLookupEnrichment(p$enrichment, gInfo, q) + enRow <- .colocLookupEnrichment(enrichment, gInfo, q) if (is.na(enRow)) { msg <- glue( "colocPipeline: no enrichment entry for ", @@ -449,31 +549,31 @@ colocPipeline <- function( warn(msg) enRow <- 0 } - list(enRow = enRow, p12Used = min(p$p12 * (1 + enRow), p$p12Max)) + list(enRow = enRow, p12Used = min(p12 * (1 + enRow), p12Max)) } # Run coloc.bf_bf for an aligned pair, warning + NULL on failure. # @noRd -.colocRunPair <- function(aligned, p, p12Used, q, gInfo) { - colocArgs <- c( +#' @importFrom rlang try_fetch +.colocRunPair <- function(aligned, p12Used, q, gInfo, p1, p2, colocArgs) { + callArgs <- c( list( aligned$qtl, aligned$gwas, - p1 = p$p1, - p2 = p$p2, + p1 = p1, + p2 = p2, p12 = p12Used ), - p$dots + colocArgs ) - tryCatch( - exec(coloc::coloc.bf_bf, !!!colocArgs), - error = function(e) { + try_fetch( + exec(coloc::coloc.bf_bf, !!!callArgs), + error = function(cnd) { msg <- glue( "colocPipeline: coloc.bf_bf failed for ", - "{q$label} x {gInfo$label}: ", - "{conditionMessage(e)}" + "{q$label} x {gInfo$label}" ) - warn(msg) + warn(msg, parent = cnd) NULL } ) @@ -481,7 +581,13 @@ colocPipeline <- function( # Build a coloc summary row carrying the QTL / GWAS identity + enrichment. # @noRd -.colocSummaryRow <- function(pairRes, q, gInfo, p, p12Info) { +.colocSummaryRow <- function( + pairRes, + q, + gInfo, + useEnrichment, + p12Info +) { sm <- as.data.frame(pairRes$summary, stringsAsFactors = FALSE) sm <- .colocRenameNsnps(sm) sm$study <- q$study @@ -522,7 +628,7 @@ colocPipeline <- function( fill = NA_integer_ ) sm$blockId <- gInfo$blockId %||% NA_character_ - if (p$useEnrichment) { + if (useEnrichment) { sm$enrichment <- p12Info$enRow sm$p12Used <- p12Info$p12Used } @@ -531,10 +637,21 @@ colocPipeline <- function( # Assemble the result table + attach the GWAS fine-mapping when requested. # @noRd -.colocFinalize <- function(results, p) { - out <- .colocAssemble(results, p$useEnrichment, .colocLdSketch(p)) - if (p$returnGwasFineMapping && methods::is(p$gwasInput, "SumStatsBase")) { - attr(out, "gwasFineMapping") <- p$gwasFmr +.colocFinalize <- function( + results, + useEnrichment, + qtlFineMappingResult, + gwasFmr, + gwasInput, + returnGwasFineMapping +) { + out <- .colocAssemble( + results, + useEnrichment, + .colocLdSketch(qtlFineMappingResult, gwasFmr) + ) + if (returnGwasFineMapping && methods::is(gwasInput, "SumStatsBase")) { + attr(out, "gwasFineMapping") <- gwasFmr } out } @@ -546,7 +663,7 @@ colocPipeline <- function( return(.colocEmptyResult(enriched = useEnrichment, ldSketch = ldSketch)) } rows <- bind_rows(map(map(results, "rows"), .colocStandardiseRow)) - variants <- unlist(map(results, "variants"), recursive = FALSE) + variants <- list_c(map(results, "variants")) ColocResult(.colocOrderColumns(rows, useEnrichment), variants, ldSketch) } @@ -729,13 +846,13 @@ colocPipeline <- function( return(csIdx) } } else if (!is.null(filterLbfCsSecondary)) { - secIdx <- tryCatch( + secIdx <- try_fetch( .colocFilterCsByConcentration( fit, coverage = filterLbfCsSecondary, concentration = filterLbfCsConcentration ), - error = function(e) NULL + error = function(cnd) NULL ) if (!is.null(secIdx) && length(secIdx) > 0L) { return(secIdx) @@ -1020,8 +1137,32 @@ colocPipeline <- function( # Score the first side's LBF against one second-side record -> a summary row # (or NULL). # @noRd -.colocScorePairAt <- function(gInfo, qLbfInfo, p, q) { - .colocScorePair(qLbfInfo$lbf, gInfo, q, p) +.colocScorePairAt <- function( + gInfo, + qLbfInfo, + q, + useEnrichment, + enrichment, + p12, + p12Max, + p1, + p2, + alleleFlip, + colocArgs +) { + .colocScorePair( + qLbfInfo$lbf, + gInfo, + q, + useEnrichment = useEnrichment, + enrichment = enrichment, + p12 = p12, + p12Max = p12Max, + p1 = p1, + p2 = p2, + alleleFlip = alleleFlip, + colocArgs = colocArgs + ) } # TRUE when a credible set has fewer than `maxSize` variants. diff --git a/R/colocboostPipeline.R b/R/colocboostPipeline.R index af01227f8..197d1db8c 100644 --- a/R/colocboostPipeline.R +++ b/R/colocboostPipeline.R @@ -120,9 +120,10 @@ #' swaps recognized (flipping z / residualized dosage / LD to a shared #' coding); when FALSE, match on exact alleles only (names-only), so a ref/alt #' swap is treated as a distinct variant. -#' @param ... Additional arguments forwarded to -#' \code{\link[colocboost]{colocboost}} (e.g., \code{M}, \code{L}, -#' \code{output_level}). +#' @param colocboostArgs Optional named list of additional arguments +#' forwarded to \code{\link[colocboost]{colocboost}} (e.g., \code{M}, +#' \code{L}, \code{output_level}). +#' @param ... Required by the generic; the methods take no further arguments. #' @return A \code{\linkS4class{ColocBoostResult}}: one element per #' confidence set (CoS) across every analysis that ran, holding that set's #' member variants with their \code{vcp}. The \code{analysis} column marks @@ -162,18 +163,18 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { # ============================================================================= # Run colocboost() with tryCatch + timing. +#' @importFrom rlang try_fetch .cbRun <- function(label, args) { if (!requireNamespace("colocboost", quietly = TRUE)) { abort("The colocboost package is required for colocboostPipeline().") } t1 <- Sys.time() args <- compact(args) - res <- tryCatch( + res <- try_fetch( exec(colocboost::colocboost, !!!args), - error = function(e) { - eMsg <- conditionMessage(e) - msg <- glue("{label} failed: {eMsg}") - inform(msg) + error = function(cnd) { + msg <- glue("{label} failed") + inform(msg, parent = cnd) NULL } ) @@ -398,20 +399,19 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { # Residualized phenotypes for one context (message + NULL on failure). # @noRd .cbResidualizedY <- function(qd, ctx, traitId, region) { - tryCatch( + try_fetch( getResidualizedPhenotypes( qd, contexts = ctx, traitId = traitId, region = region ), - error = function(e) { - eMsg <- conditionMessage(e) + error = function(cnd) { msg <- glue( "colocboostPipeline: skipping context '{ctx}' ", - "(residualized phenotypes unavailable: {eMsg})." + "(residualized phenotypes unavailable)." ) - inform(msg) + inform(msg, parent = cnd) NULL } ) @@ -420,7 +420,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { # Residualized genotypes for one context (message + NULL on failure). # @noRd .cbResidualizedX <- function(qd, ctx, traitId, region, cisWindow, samples) { - tryCatch( + try_fetch( getResidualizedGenotypes( qd, contexts = ctx, @@ -429,13 +429,12 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { cisWindow = cisWindow, samples = samples ), - error = function(e) { - eMsg <- conditionMessage(e) + error = function(cnd) { msg <- glue( "colocboostPipeline: skipping context '{ctx}' ", - "(residualized genotypes unavailable: {eMsg})." + "(residualized genotypes unavailable)." ) - inform(msg) + inform(msg, parent = cnd) NULL } ) @@ -488,7 +487,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { # list(YSplit, dict). (Sequential make.unique naming -- kept as a loop.) # @noRd .cbSplitY <- function(YperCtx, xMatch) { - allTraitNames <- unlist(map(YperCtx, colnames), use.names = FALSE) + allTraitNames <- unname(list_c(map(YperCtx, colnames))) dupTraits <- unique(allTraitNames[ duplicated(allTraitNames) | duplicated(allTraitNames, fromLast = TRUE) ]) @@ -816,7 +815,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { jointGwas, separateGwas, focalTrait, - dotArgs, + colocboostArgs, qtlLdSketch = NULL, qtlSumstatBundle = NULL ) { @@ -840,7 +839,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { qtlSumstatBundle, hasInd, focalTrait, - dotArgs + colocboostArgs ) results$xqtl_coloc <- run$result results$computing_time$Analysis$xqtl_coloc <- run$time @@ -854,7 +853,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { individualBundle, sumstatBundle, hasInd, - dotArgs + colocboostArgs ) results$joint_gwas <- run$result results$computing_time$Analysis$joint_gwas <- run$time @@ -868,7 +867,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { individualBundle, sumstatBundle, hasInd, - dotArgs + colocboostArgs ) results$separate_gwas <- run$result results$computing_time$Analysis$separate_gwas <- run$time @@ -950,7 +949,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { sumstatBundle, hasInd, focalTrait, - dotArgs + colocboostArgs ) { traits <- c( if (hasInd) individualBundle$outcomeNames else character(), @@ -991,7 +990,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { output_level = 2 ), ldArgs, - dotArgs + colocboostArgs ) run <- .cbRun("xQTL-only ColocBoost", args) list(result = run$result, time = run$time) @@ -1039,7 +1038,12 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { # Joint (non-focal) QTL + GWAS run -> list(result, time). # @noRd -.cbRunJointGwas <- function(individualBundle, sumstatBundle, hasInd, dotArgs) { +.cbRunJointGwas <- function( + individualBundle, + sumstatBundle, + hasInd, + colocboostArgs +) { traits <- c( if (hasInd) individualBundle$outcomeNames else character(), names(sumstatBundle$sumstat) @@ -1064,7 +1068,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { output_level = 2 ), ldArgs, - dotArgs + colocboostArgs ) run <- .cbRun("Joint GWAS ColocBoost", args) list(result = run$result, time = run$time) @@ -1076,7 +1080,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { individualBundle, sumstatBundle, hasInd, - dotArgs + colocboostArgs ) { ssNames <- names(sumstatBundle$sumstat) t1 <- Sys.time() @@ -1088,7 +1092,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { individualBundle = individualBundle, sumstatBundle = sumstatBundle, hasInd = hasInd, - dotArgs = dotArgs + colocboostArgs = colocboostArgs ), ssNames ) @@ -1115,7 +1119,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { individualBundle, sumstatBundle, hasInd, - dotArgs + colocboostArgs ) { ldIdx <- sumstatBundle$dict_sumstatLD[i, 2L] ldArgs <- .cbBuildLdArgs(sumstatBundle$LD[ldIdx]) @@ -1140,7 +1144,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { output_level = 2 ), ldArgs, - dotArgs + colocboostArgs ) .cbRun(str_c("Separate GWAS ColocBoost for ", study), args)$result } @@ -1209,12 +1213,12 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { if (!is.null(individualBundle)) { ids <- c( ids, - unlist(map(individualBundle$X, colnames), use.names = FALSE) + unname(list_c(map(individualBundle$X, colnames))) ) } ids <- c( ids, - unlist(map(pairs, list("sumstat", "variant")), use.names = FALSE) + unname(list_c(map(pairs, list("sumstat", "variant")))) ) ids <- unique(ids[!is.na(ids)]) if (length(ids) == 0L) { @@ -1248,7 +1252,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { jointGwas, separateGwas, focalTrait, - dotArgs, + colocboostArgs, qtlLdSketch = NULL, alleleFlip = TRUE, cutoffs = NULL @@ -1286,7 +1290,7 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { jointGwas, separateGwas, focalTrait, - dotArgs, + colocboostArgs, qtlLdSketch = qtlLdSketch, qtlSumstatBundle = qtlSumstatBundle ) @@ -1328,45 +1332,64 @@ setGeneric("colocboostPipeline", function(qtlData, gwasSumStats = NULL, ...) { # ============================================================================= # The QtlDataset colocboost run: screen spec, individual-level bundle, shared -# driver. Split out so the method is only its signature -- twenty formals plus -# a body is past the length a reviewer (or BiocCheck) will accept, and the -# signature is the part that cannot be shortened. -# `p` is the method's named arguments; `dots` is its `...`, which -# as.list(environment()) does not capture. +# driver. Split out so the method body stays short; the arguments are passed +# explicitly rather than bundled, so a rename is a compile-time error and the +# dependency of each helper is visible. # @noRd -.cbQtlDatasetDrive <- function(p, dots) { +.cbQtlDatasetDrive <- function( + qtlData, + gwasSumStats, + contexts, + traitId, + region, + cisWindow, + focalTrait, + xqtlColoc, + jointGwas, + separateGwas, + samples, + mafCutoff, + macCutoff, + imissCutoff, + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip, + alleleFlip, + colocboostArgs +) { screenSpec <- .cbScreenSpec( - p$pipCutoffToSkip, - p$absZCutoffToSkip, - p$bfCutoffToSkip, - p$logBfCutoffToSkip + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip ) indBundle <- .cbIndividualBundle( - p$qtlData, - contexts = p$contexts, - traitId = p$traitId, - region = p$region, - cisWindow = p$cisWindow, - samples = p$samples, + qtlData, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + samples = samples, pipCutoffToSkip = screenSpec ) .cbDriver( indBundle, qtlPairs = list(), - p$gwasSumStats, - p$xqtlColoc, - p$jointGwas, - p$separateGwas, - p$focalTrait, - dots, - alleleFlip = p$alleleFlip, + gwasSumStats, + xqtlColoc, + jointGwas, + separateGwas, + focalTrait, + colocboostArgs, + alleleFlip = alleleFlip, # The QTL side is individual-level here, but the GWAS side may still # be sumstats, so the panel cutoffs still apply to it. - cutoffs = .panelCutoffs(list( - mafCutoff = p$mafCutoff, - macCutoff = p$macCutoff, - imissCutoff = p$imissCutoff - )) + cutoffs = .panelCutoffs( + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ) ) } @@ -1396,9 +1419,30 @@ setMethod( bfCutoffToSkip = 0, logBfCutoffToSkip = 0, alleleFlip = TRUE, - ... + colocboostArgs = list() ) { - .cbQtlDatasetDrive(as.list(environment()), list(...)) + .cbQtlDatasetDrive( + qtlData = qtlData, + gwasSumStats = gwasSumStats, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + focalTrait = focalTrait, + xqtlColoc = xqtlColoc, + jointGwas = jointGwas, + separateGwas = separateGwas, + samples = samples, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff, + pipCutoffToSkip = pipCutoffToSkip, + absZCutoffToSkip = absZCutoffToSkip, + bfCutoffToSkip = bfCutoffToSkip, + logBfCutoffToSkip = logBfCutoffToSkip, + alleleFlip = alleleFlip, + colocboostArgs = colocboostArgs + ) } ) @@ -1422,15 +1466,14 @@ setMethod( mafCutoff = 0, macCutoff = 0, imissCutoff = 1, - ... + colocboostArgs = list() ) { .cbRequireSumStatsQc(qtlData, "qtlData") - dotArgs <- list(...) - cutoffs <- .panelCutoffs(list( + cutoffs <- .panelCutoffs( mafCutoff = mafCutoff, macCutoff = macCutoff, imissCutoff = imissCutoff - )) + ) qtlPairs <- .cbQtlSumStatsBundle( qtlData, contexts = contexts, @@ -1445,7 +1488,7 @@ setMethod( jointGwas = jointGwas, separateGwas = separateGwas, focalTrait = focalTrait, - dotArgs = dotArgs, + colocboostArgs = colocboostArgs, qtlLdSketch = getLdSketch(qtlData), alleleFlip = alleleFlip, cutoffs = cutoffs @@ -1478,43 +1521,95 @@ setMethod( bfCutoffToSkip = 0, logBfCutoffToSkip = 0, alleleFlip = TRUE, - ... + colocboostArgs = list() ) { - p <- as.list(environment()) - p$dotArgs <- list(...) - .cbPipelineMultiStudy(p) + .cbPipelineMultiStudy( + qtlData = qtlData, + gwasSumStats = gwasSumStats, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + focalTrait = focalTrait, + xqtlColoc = xqtlColoc, + jointGwas = jointGwas, + separateGwas = separateGwas, + samples = samples, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff, + pipCutoffToSkip = pipCutoffToSkip, + absZCutoffToSkip = absZCutoffToSkip, + bfCutoffToSkip = bfCutoffToSkip, + logBfCutoffToSkip = logBfCutoffToSkip, + alleleFlip = alleleFlip, + colocboostArgs = colocboostArgs + ) } ) # MultiStudyQtlDataset colocboost worker: aggregate the per-study individual # bundles + embedded sumstats, then dispatch to the shared driver. # @noRd -.cbPipelineMultiStudy <- function(p) { +.cbPipelineMultiStudy <- function( + qtlData, + gwasSumStats, + contexts, + traitId, + region, + cisWindow, + focalTrait, + xqtlColoc, + jointGwas, + separateGwas, + samples, + mafCutoff, + macCutoff, + imissCutoff, + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip, + alleleFlip, + colocboostArgs +) { screenSpec <- .cbScreenSpec( - p$pipCutoffToSkip, - p$absZCutoffToSkip, - p$bfCutoffToSkip, - p$logBfCutoffToSkip + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip + ) + indBundle <- .cbMultiStudyIndBundle( + qtlData = qtlData, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + samples = samples, + screenSpec = screenSpec + ) + cutoffs <- .panelCutoffs( + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff ) - indBundle <- .cbMultiStudyIndBundle(p, screenSpec) - cutoffs <- .panelCutoffs(p) ss <- .cbMultiStudySumstats( - p$qtlData, - p$contexts, - p$traitId, + qtlData, + contexts, + traitId, cutoffs = cutoffs ) .cbDriver( indBundle, ss$qtlPairs, - p$gwasSumStats, - p$xqtlColoc, - p$jointGwas, - p$separateGwas, - p$focalTrait, - p$dotArgs, + gwasSumStats, + xqtlColoc, + jointGwas, + separateGwas, + focalTrait, + colocboostArgs, qtlLdSketch = ss$qtlLdSketch, - alleleFlip = p$alleleFlip, + alleleFlip = alleleFlip, cutoffs = cutoffs ) } @@ -1524,8 +1619,16 @@ setMethod( # outcomes when two studies share a trait. Returns the combined bundle or NULL. # (Sequential offset-shifted merge -- kept as a loop.) # @noRd -.cbMultiStudyIndBundle <- function(p, screenSpec) { - qtlDatasets <- getQtlDatasets(p$qtlData) +.cbMultiStudyIndBundle <- function( + qtlData, + contexts, + traitId, + region, + cisWindow, + samples, + screenSpec +) { + qtlDatasets <- getQtlDatasets(qtlData) combinedX <- list() combinedY <- list() combinedDict <- matrix(integer(0), ncol = 2L) @@ -1535,11 +1638,11 @@ setMethod( for (study in names(qtlDatasets)) { sub <- .cbIndividualBundle( qtlDatasets[[study]], - contexts = p$contexts, - traitId = p$traitId, - region = p$region, - cisWindow = p$cisWindow, - samples = p$samples, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + samples = samples, pipCutoffToSkip = screenSpec ) if (is.null(sub)) { @@ -1649,7 +1752,7 @@ setMethod( individualBundle, sumstatBundle, hasInd, - dotArgs + colocboostArgs ) { .cbRunOneSeparateGwas( i, @@ -1657,6 +1760,6 @@ setMethod( individualBundle, sumstatBundle, hasInd, - dotArgs + colocboostArgs ) } diff --git a/R/crossValidation.R b/R/crossValidation.R index 2e85d295f..c3ee919fe 100644 --- a/R/crossValidation.R +++ b/R/crossValidation.R @@ -170,13 +170,12 @@ # Validate inputs, coerce a vector Y to a one-column matrix, and set stable # row/column dimnames on X and Y. Returns list(X, Y). # @noRd +#' @importFrom checkmate assert assertCount assertMatrix +#' @importFrom checkmate checkAtomicVector checkMatrix .cvPrepareData <- function(X, Y, fold, verbose) { - if (!is.null(fold) && (!is.numeric(fold) || fold <= 0)) { - abort("Invalid value for 'fold'. It must be a positive integer.") - } - if (!is.matrix(X) || (!is.matrix(Y) && !is.vector(Y))) { - abort("X must be a matrix and Y must be a matrix or a vector.") - } + assertCount(fold, positive = TRUE, null.ok = TRUE) + assertMatrix(X) + assert(checkMatrix(Y), checkAtomicVector(Y), .var.name = "Y") if (is.vector(Y)) { Y <- matrix(Y, ncol = 1) if (verbose >= 1) { @@ -187,9 +186,7 @@ inform(msg) } } - if (nrow(X) != nrow(Y)) { - abort("The number of rows in X and Y must be the same.") - } + assertMatrix(Y, nrows = nrow(X)) .cvSetDimnames(X, Y) } @@ -377,7 +374,7 @@ # @noRd .cvAggregate <- function(foldResults, Y, verbose) { metricNames <- c("corr", "rsq", "adj_rsq", "pval", "RMSE", "MAE") - methodKeys <- unique(unlist(map(foldResults, .cvPredNames))) + methodKeys <- unique(list_c(map(foldResults, .cvPredNames))) prediction <- list() performance <- list() for (mk in methodKeys) { diff --git a/R/ctwasPipeline.R b/R/ctwasPipeline.R index b0d2e3c19..8be501abc 100644 --- a/R/ctwasPipeline.R +++ b/R/ctwasPipeline.R @@ -104,7 +104,8 @@ #' \code{mergeBoundary = TRUE}. #' @param mergeMaxSNP Numeric (length 1). Per-merged-region SNP cap. Default #' \code{Inf}. Ignored unless \code{mergeBoundary = TRUE}. -#' @param ... Additional arguments forwarded to \code{ctwas::ctwas_sumstats}. +#' @param ctwasArgs Optional named list of additional arguments +#' forwarded to \code{ctwas::ctwas_sumstats}. #' @return A \code{\link{CtwasResult}} collection: one row per \code{(gwasStudy, #' study, context, method)}. A single-context run is one row per method; a #' multi-context (joint) run emits per-context rows sharing the same @@ -163,15 +164,38 @@ ctwasPipeline <- function( mergePipThresh = 0.5, mergeFilterCs = FALSE, mergeMaxSNP = Inf, - ... + ctwasArgs = list() ) { groupPriorVarStructure <- arg_match(groupPriorVarStructure) .ctwasRequireNamedLists(gwasSumStats, twasWeights) methods <- .ctwasResolveMethods(twasWeights, method) gwasStudy <- .ctwasGwasStudy(gwasSumStats) - cfg <- as.list(environment()) - cfg$dots <- list(...) - rows <- list_flatten(map(methods, .ctwasRunMethod, cfg = cfg)) + rows <- list_flatten(map( + methods, + .ctwasRunMethod, + gwasSumStats = gwasSumStats, + twasWeights = twasWeights, + twasZ = twasZ, + fineMappingResult = fineMappingResult, + twasWeightCutoff = twasWeightCutoff, + csMinCor = csMinCor, + minPipCutoff = minPipCutoff, + maxNumVariants = maxNumVariants, + thin = thin, + niterPrefit = niterPrefit, + niter = niter, + groupPriorVarStructure = groupPriorVarStructure, + ncore = ncore, + fallbackToPrefit = fallbackToPrefit, + L = L, + mergeBoundary = mergeBoundary, + mergePipThresh = mergePipThresh, + mergeFilterCs = mergeFilterCs, + mergeMaxSNP = mergeMaxSNP, + gwasStudy = gwasStudy, + keepSnps = keepSnps, + ctwasArgs = ctwasArgs + )) if (length(rows) == 0L) { msg <- glue( "ctwasPipeline: no genes were modeled (the weight sources ", @@ -185,62 +209,109 @@ ctwasPipeline <- function( # One cTWAS run for method `m`: assemble inputs -> estimate params -> screen -> # fine-map (optionally boundary-merge) -> per-context row-specs. `cfg` bundles -# the ctwasPipeline arguments (incl. `dots` = the forwarded `...`). +# the ctwasPipeline arguments (incl. the `ctwasArgs` option list). # @noRd -.ctwasRunMethod <- function(m, cfg) { +.ctwasRunMethod <- function( + m, + gwasSumStats, + twasWeights, + twasZ, + fineMappingResult, + twasWeightCutoff, + csMinCor, + minPipCutoff, + maxNumVariants, + thin, + niterPrefit, + niter, + groupPriorVarStructure, + ncore, + fallbackToPrefit, + L, + mergeBoundary, + mergePipThresh, + mergeFilterCs, + mergeMaxSNP, + gwasStudy, + keepSnps, + ctwasArgs +) { inputs <- assembleCtwasInputs( - gwasSumStats = cfg$gwasSumStats, - twasWeights = cfg$twasWeights, - twasZ = cfg$twasZ, - fineMappingResult = cfg$fineMappingResult, + gwasSumStats = gwasSumStats, + twasWeights = twasWeights, + twasZ = twasZ, + fineMappingResult = fineMappingResult, method = m, - twasWeightCutoff = cfg$twasWeightCutoff, - csMinCor = cfg$csMinCor, - minPipCutoff = cfg$minPipCutoff, - maxNumVariants = cfg$maxNumVariants + twasWeightCutoff = twasWeightCutoff, + csMinCor = csMinCor, + minPipCutoff = minPipCutoff, + maxNumVariants = maxNumVariants ) - estArgs <- c( - list( - inputs, - thin = cfg$thin, - niterPrefit = cfg$niterPrefit, - niter = cfg$niter, - groupPriorVarStructure = cfg$groupPriorVarStructure, - ncore = cfg$ncore, - fallbackToPrefit = cfg$fallbackToPrefit - ), - cfg$dots + # The three stages each take the ctwas option list as one named argument + # now, so it is passed rather than spliced in as loose top-level args. + est <- estCtwasParam( + inputs, + thin = thin, + niterPrefit = niterPrefit, + niter = niter, + groupPriorVarStructure = groupPriorVarStructure, + ncore = ncore, + fallbackToPrefit = fallbackToPrefit, + ctwasArgs = ctwasArgs + ) + screened <- screenCtwasRegions( + est, + L = L, + ncore = ncore, + ctwasArgs = ctwasArgs ) - est <- exec(estCtwasParam, !!!estArgs) - screenArgs <- c(list(est, L = cfg$L, ncore = cfg$ncore), cfg$dots) - screened <- exec(screenCtwasRegions, !!!screenArgs) - finemapArgs <- c(list(screened, L = cfg$L, ncore = cfg$ncore), cfg$dots) - finemap <- exec(finemapCtwasRegions, !!!finemapArgs) - if (cfg$mergeBoundary) { - finemap <- .ctwasMaybeMerge(finemap, cfg) + finemap <- finemapCtwasRegions( + screened, + L = L, + ncore = ncore, + ctwasArgs = ctwasArgs + ) + if (mergeBoundary) { + finemap <- .ctwasMaybeMerge( + finemap, + mergePipThresh = mergePipThresh, + mergeFilterCs = mergeFilterCs, + mergeMaxSNP = mergeMaxSNP, + L = L, + ncore = ncore, + ctwasArgs = ctwasArgs + ) } .ctwasRunToRows( finemap, - gwasStudy = cfg$gwasStudy, + gwasStudy = gwasStudy, method = m, - keepSnps = cfg$keepSnps + keepSnps = keepSnps ) } # Boundary-gene region merging: split a high-PIP straddling gene's adjacent # regions and re-fine-map. Merge-transparent downstream (keyed by gene id). # @noRd -.ctwasMaybeMerge <- function(finemap, cfg) { +.ctwasMaybeMerge <- function( + finemap, + mergePipThresh, + mergeFilterCs, + mergeMaxSNP, + L, + ncore, + ctwasArgs +) { mergeArgs <- c( list( finemap, - pipThresh = cfg$mergePipThresh, - filterCs = cfg$mergeFilterCs, - maxSNP = cfg$mergeMaxSNP, - L = cfg$L, - ncore = cfg$ncore + pipThresh = mergePipThresh, + filterCs = mergeFilterCs, + maxSNP = mergeMaxSNP, + L = L, + ncore = ncore ), - cfg$dots + ctwasArgs ) exec(mergeCtwasBoundaryRegions, !!!mergeArgs) } @@ -329,11 +400,7 @@ assembleCtwasInputs <- function( # @noRd .ctwasValidateGwasList <- function(gwasSumStats) { if (!requireNamespace("ctwas", quietly = TRUE)) { - msg <- glue( - "Package 'ctwas' is required for the cTWAS pipeline. ", - "Install from https://github.com/xinhe-lab/ctwas ." - ) - abort(msg) + abort("Package 'ctwas' is required for the cTWAS pipeline.") } if (missing(gwasSumStats) || !methods::is(gwasSumStats, "GwasSumStats")) { msg <- glue( @@ -626,7 +693,8 @@ assembleCtwasInputs <- function( #' deliberately broad; a genuinely broken input still surfaces because the #' prefit re-run will itself error. Mirrors the legacy ctwas_2 workaround on #' toy data where the accurate EM cannot be estimated. -#' @param ... Additional arguments forwarded to \code{ctwas::est_param} (e.g. +#' @param ctwasArgs Optional named list of additional arguments +#' forwarded to \code{ctwas::est_param} (e.g. #' \code{min_p_single_effect}, \code{min_group_size}). #' @return The \code{inputs} list augmented with \code{region_data}, #' \code{boundary_genes}, \code{z_gene}, and \code{param}. @@ -650,7 +718,7 @@ estCtwasParam <- function( ), ncore = 1L, fallbackToPrefit = FALSE, - ... + ctwasArgs = list() ) { if (!requireNamespace("ctwas", quietly = TRUE)) { abort("Package 'ctwas' is required for estCtwasParam.") @@ -658,10 +726,15 @@ estCtwasParam <- function( groupPriorVarStructure <- arg_match(groupPriorVarStructure) ncore <- as.integer(ncore) inputs <- .ctwasResolveLdPaths(inputs) - extra <- list(...) zGene <- .ctwasEnsureZGene(inputs, ncore) - regionData <- .ctwasAssembleRegionData(inputs, zGene, thin, ncore, extra) - boundaryGenes <- .ctwasBoundaryGenes(inputs, ncore, extra) + regionData <- .ctwasAssembleRegionData( + inputs, + zGene, + thin, + ncore, + ctwasArgs + ) + boundaryGenes <- .ctwasBoundaryGenes(inputs, ncore, ctwasArgs) paramRes <- .ctwasEstParamOrFallback( regionData, niterPrefit, @@ -670,7 +743,7 @@ estCtwasParam <- function( ncore, thin, fallbackToPrefit, - extra + ctwasArgs ) # assemble_region_data does not echo z_gene back, so propagate the # precomputed z_gene we passed in (inputs$z_gene is NULL when twasZ was not @@ -766,6 +839,7 @@ estCtwasParam <- function( # log-likelihood), so catch all rather than match brittle version messages. The # prefit re-run runs to full `niter` (this is now the final prior). # @noRd +#' @importFrom rlang try_fetch .ctwasEstParamOrFallback <- function( regionData, niterPrefit, @@ -788,7 +862,7 @@ estCtwasParam <- function( extra )) } - tryCatch( + try_fetch( .ctwasEstParamAccurate( regionData, niterPrefit, @@ -797,12 +871,12 @@ estCtwasParam <- function( ncore, extra ), - error = function(e) { + error = function(cnd) { msg <- glue( - "estCtwasParam: accurate EM unusable ", - "({conditionMessage(e)}); falling back to prefit estimates." + "estCtwasParam: accurate EM unusable; falling back to ", + "prefit estimates." ) - inform(msg) + inform(msg, parent = cnd) .ctwasFitPrefitEm( regionData, niter = as.integer(niter), @@ -829,7 +903,8 @@ estCtwasParam <- function( #' single-effect (SER) model and ignores L. \code{L} is applied by #' \code{\link{finemapCtwasRegions}} downstream. #' @param ncore Number of cores. -#' @param ... Additional arguments forwarded to \code{ctwas::screen_regions} +#' @param ctwasArgs Optional named list of additional arguments +#' forwarded to \code{ctwas::screen_regions} #' (e.g. \code{min_nonSNP_PIP}, \code{min_snp_pval}, \code{min_var}, #' \code{min_gene}). #' @return The \code{estResult} list augmented with \code{screen_res} (the full @@ -839,7 +914,12 @@ estCtwasParam <- function( #' data(ctwasEstExample) #' screenCtwasRegions(ctwasEstExample, L = 5L) #' @export -screenCtwasRegions <- function(estResult, L = 5L, ncore = 1L, ...) { +screenCtwasRegions <- function( + estResult, + L = 5L, + ncore = 1L, + ctwasArgs = list() +) { if (!requireNamespace("ctwas", quietly = TRUE)) { abort("Package 'ctwas' is required for screenCtwasRegions.") } @@ -848,7 +928,7 @@ screenCtwasRegions <- function(estResult, L = 5L, ncore = 1L, ...) { # thinned set first when assemble_region_data was called with thin < 1 # (matches ctwas_sumstats's own expand-before-screen step). thinVals <- compact(map(estResult$region_data, "thin")) - needsExpand <- length(thinVals) > 0L && min(unlist(thinVals)) < 1 + needsExpand <- length(thinVals) > 0L && min(list_c(thinVals)) < 1 regionDataForScreen <- if (needsExpand) { .ctwasInvoke( ctwas::expand_region_data, @@ -858,7 +938,7 @@ screenCtwasRegions <- function(estResult, L = 5L, ncore = 1L, ...) { z_snp = estResult$z_snp, ncore = as.integer(ncore) ), - extra = list(...) + extra = ctwasArgs ) } else { estResult$region_data @@ -871,7 +951,7 @@ screenCtwasRegions <- function(estResult, L = 5L, ncore = 1L, ...) { group_prior_var = estResult$param$group_prior_var, ncore = as.integer(ncore) ), - extra = list(...) + extra = ctwasArgs ) c( estResult, @@ -894,7 +974,8 @@ screenCtwasRegions <- function(estResult, L = 5L, ncore = 1L, ...) { #' @param screenResult A list returned by \code{\link{screenCtwasRegions}}. #' @param L Pass-through. #' @param ncore Number of cores. -#' @param ... Additional arguments forwarded to \code{ctwas::finemap_regions}. +#' @param ctwasArgs Optional named list of additional arguments +#' forwarded to \code{ctwas::finemap_regions}. #' @return A list mirroring \code{ctwas::ctwas_sumstats}'s output: #' \code{z_gene}, \code{param}, \code{finemap_res}, \code{susie_alpha_res}, #' \code{region_data}, \code{boundary_genes}, \code{screen_res}. @@ -919,7 +1000,12 @@ screenCtwasRegions <- function(estResult, L = 5L, ncore = 1L, ...) { #' screened <- screenCtwasRegions(est, L = 5L) #' finemapCtwasRegions(screened, L = 5L) #' @export -finemapCtwasRegions <- function(screenResult, L = 5L, ncore = 1L, ...) { +finemapCtwasRegions <- function( + screenResult, + L = 5L, + ncore = 1L, + ctwasArgs = list() +) { if (!requireNamespace("ctwas", quietly = TRUE)) { abort("Package 'ctwas' is required for finemapCtwasRegions.") } @@ -942,7 +1028,7 @@ finemapCtwasRegions <- function(screenResult, L = 5L, ncore = 1L, ...) { snpinfo_loader_fun = screenResult$snpinfo_loader_fun, ncore = as.integer(ncore) ), - extra = list(...) + extra = ctwasArgs ) } # Repair cTWAS's molecular_id mislabel (first-"|" split of our composite @@ -999,7 +1085,9 @@ finemapCtwasRegions <- function(screenResult, L = 5L, ncore = 1L, ...) { #' @param L Integer. Max number of single effects for the merged-region #' re-fine-mapping (LD path only). Default \code{5}. #' @param ncore Number of cores. Default \code{1}. -#' @param ... Forwarded to the underlying ctwas postprocess function. +#' @param ctwasArgs Optional named list of additional arguments +#' forwarded to the underlying ctwas postprocess +#' function. #' @return The \code{finemapResult} list with \code{finemap_res}, #' \code{susie_alpha_res}, \code{region_data}, \code{region_info}, #' \code{LD_map}, and \code{snp_map} replaced by the post-merge ("updated") @@ -1017,7 +1105,7 @@ mergeCtwasBoundaryRegions <- function( maxSNP = Inf, L = 5L, ncore = 1L, - ... + ctwasArgs = list() ) { if (!requireNamespace("ctwas", quietly = TRUE)) { abort("Package 'ctwas' is required for mergeCtwasBoundaryRegions.") @@ -1040,7 +1128,7 @@ mergeCtwasBoundaryRegions <- function( ncore ) fa <- .ctwasMergeDispatch(finemapResult, common, L) - userExtra <- list(...) + userExtra <- ctwasArgs userExtra <- userExtra[setdiff(names(userExtra), names(fa$args))] callArgs <- c(fa$args, userExtra) res <- exec(fa$fn, !!!callArgs) @@ -1134,7 +1222,7 @@ mergeCtwasBoundaryRegions <- function( # meant for a sibling step doesn't crash this one -- and so args # that fn would otherwise forward via its own `...` (e.g. into # susie_rss) don't bleed into incompatible downstream functions. - formalsFn <- tryCatch(names(formals(fn)), error = function(e) NULL) + formalsFn <- try_fetch(names(formals(fn)), error = function(cnd) NULL) if (!is.null(formalsFn)) { explicitFormals <- setdiff(formalsFn, "...") extra <- extra[intersect(names(extra), explicitFormals)] @@ -1253,7 +1341,7 @@ mergeCtwasBoundaryRegions <- function( # - Otherwise: error. # @noRd .ctwasResolveMethod <- function(twasWeightsList, method = NULL) { - available <- unique(unlist(map(twasWeightsList, .ctwasMethodChr))) + available <- unique(list_c(map(twasWeightsList, .ctwasMethodChr))) if (length(available) == 0L) { abort("ctwasPipeline: TwasWeights collections have no method entries.") } @@ -1382,13 +1470,13 @@ mergeCtwasBoundaryRegions <- function( start <- integer(n) end <- integer(n) for (i in seq_len(n)) { - g <- tryCatch( + g <- try_fetch( asGranges(str_replace( as.character(ids[[i]]), "_([0-9]+)_([0-9]+)$", ":\\1-\\2" )), - error = function(e) NULL + error = function(cnd) NULL ) if (!is.null(g) && length(g) >= 1L) { chrom[[i]] <- as.character(GenomicRanges::seqnames(g))[[1L]] @@ -1455,6 +1543,8 @@ mergeCtwasBoundaryRegions <- function( # @noRd .ctwasBlockSpan <- function(gss) { + # S4 dispatch, not list flattening: `gss` is a GwasSumStats collection and + # unlist() returns the GRanges that range() below needs. variants <- unlist(gss, use.names = FALSE) if (length(variants) == 0L) { return(GenomicRanges::GRanges()) @@ -1558,7 +1648,7 @@ mergeCtwasBoundaryRegions <- function( # a flat weight source .ctwasMethodsOf(twasWeightsList) } else { - unlist(map(twasWeightsList, .ctwasMethodsOf)) + list_c(compact(map(twasWeightsList, .ctwasMethodsOf))) } ) # a list of them if (length(available) == 0L) { @@ -1800,7 +1890,7 @@ mergeCtwasBoundaryRegions <- function( if (!is.null(gc) && is_in(id, names(gc))) { return(gc[[id]]) } - parsed <- tryCatch(parseVariantId(id), error = function(e) NULL) + parsed <- try_fetch(parseVariantId(id), error = function(cnd) NULL) if (is.null(parsed) || is.na(parsed$chrom[[1L]])) { return(NULL) } @@ -2041,6 +2131,7 @@ mergeCtwasBoundaryRegions <- function( m } +#' @importFrom checkmate assertFlag #' @title Structure a granular cTWAS finemap result as a CtwasResult #' @description Decompose the raw list returned by #' \code{\link{finemapCtwasRegions}} (optionally after @@ -2063,6 +2154,7 @@ mergeCtwasBoundaryRegions <- function( #' asCtwasResult(ctwasFinemapExample) #' @export asCtwasResult <- function(finemapResult, keepSnps = FALSE) { + assertFlag(keepSnps) gwasStudy <- .ctwasGwasStudyFromZSnp(finemapResult$z_snp) method <- .ctwasMethodFromWeights(finemapResult$weights) rows <- .ctwasRunToRows( @@ -2281,7 +2373,7 @@ asCtwasResult <- function(finemapResult, keepSnps = FALSE) { # Returns NULL when the entry has no variants in common with the panel. # @noRd .ctwasHarmonizeWeights <- function(origVids, origW, refVariants) { - parsed <- tryCatch(parseVariantId(origVids), error = function(e) NULL) + parsed <- try_fetch(parseVariantId(origVids), error = function(cnd) NULL) if (is.null(parsed) || nrow(parsed) == 0L) { return(NULL) } @@ -2293,7 +2385,7 @@ asCtwasResult <- function(finemapResult, keepSnps = FALSE) { w = as.numeric(origW), origIdx = seq_along(origVids) ) - res <- tryCatch( + res <- try_fetch( harmonizeAlleles( targetData = targetDf, refVariants = refVariants, @@ -2302,7 +2394,7 @@ asCtwasResult <- function(finemapResult, keepSnps = FALSE) { removeUnmatched = TRUE, removeStrandAmbiguous = TRUE ), - error = function(e) NULL + error = function(cnd) NULL ) if (is.null(res)) { return(NULL) @@ -2706,9 +2798,9 @@ asCtwasResult <- function(finemapResult, keepSnps = FALSE) { selectors$trait <- trait } selArgs <- c(list(fineMappingResult), selectors) - entry <- tryCatch( + entry <- try_fetch( exec(getFineMappingResult, !!!selArgs), - error = function(e) NULL + error = function(cnd) NULL ) if (is.null(entry)) { return(NULL) @@ -2983,11 +3075,7 @@ asCtwasResult <- function(finemapResult, keepSnps = FALSE) { if (!is.function(loader)) { return(NULL) } - env <- environment(loader) - if (is.null(env) || !exists("ldPanelsByRegion", envir = env)) { - return(NULL) - } - get("ldPanelsByRegion", envir = env) + attr(loader, "ldPanelsByRegion") } # Multi-block LD loader for ctwas. ctwas invokes @@ -2996,7 +3084,8 @@ asCtwasResult <- function(finemapResult, keepSnps = FALSE) { # `LD_map$LD_file`) into the cached per-sketch ldPanel. # @noRd .ctwasMultiBlockLdLoader <- function(ldPanelsByRegion) { - function(LD_file, ...) { + force(ldPanelsByRegion) + fn <- function(LD_file, ...) { panel <- ldPanelsByRegion[[LD_file]] if (is.null(panel)) { msg <- glue( @@ -3007,11 +3096,17 @@ asCtwasResult <- function(finemapResult, keepSnps = FALSE) { } panel$R } + # Published explicitly so .ctwasCachedPanels can recover the cache from a + # loader we built, instead of looking the name up inside the closure's + # environment. + attr(fn, "ldPanelsByRegion") <- ldPanelsByRegion + fn } # Multi-block SNP-info loader for ctwas. Mirrors the LD loader. # @noRd .ctwasMultiBlockSnpInfoLoader <- function(ldPanelsByRegion) { + force(ldPanelsByRegion) function(LD_file, ...) { panel <- ldPanelsByRegion[[LD_file]] if (is.null(panel)) { diff --git a/R/fineMappingPipeline.R b/R/fineMappingPipeline.R index bbdd5a694..477fdae43 100644 --- a/R/fineMappingPipeline.R +++ b/R/fineMappingPipeline.R @@ -837,12 +837,12 @@ setGeneric("fineMappingPipeline", function(data, ...) { # GWAS resume lookup using the GwasFineMappingResult (study, method, range) # identity; NULL when no compatible cache was supplied. # @noRd -.fmCacheLookupGwasResume <- function(p, st, tk, blockId) { +.fmCacheLookupGwasResume <- function(fineMappingResult, st, tk, blockId) { if ( - !is.null(p$fineMappingResult) && - is(p$fineMappingResult, "GwasFineMappingResult") + !is.null(fineMappingResult) && + is(fineMappingResult, "GwasFineMappingResult") ) { - .fmCacheLookupGwas(p$fineMappingResult, st, tk, blockId) + .fmCacheLookupGwas(fineMappingResult, st, tk, blockId) } else { NULL } @@ -851,11 +851,33 @@ setGeneric("fineMappingPipeline", function(data, ...) { # Fit the still-to-run RSS tokens for one GWAS region and return one row-record # per fitted token. # @noRd -.fmGwasFitRows <- function(p, gr, zn, st, blockId, toRun) { +.fmGwasFitRows <- function( + gr, + zn, + st, + blockId, + toRun, + ldSketch, + addSusieInf, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + verbose, + fullFit, + fullFitAlphaOnly, + includeAllCs, + serFallback, + rFiniteResolved, + rMismatch, + rssControl, + keepFullFit +) { z <- zn$z names(z) <- zn$variantIds ldMat <- .ldFromSketch( - p$ldSketch, + ldSketch, zn$variantIds, label = "fineMappingPipeline" ) @@ -864,24 +886,24 @@ setGeneric("fineMappingPipeline", function(data, ...) { ldMat, zn$n, toRun, - p$addSusieInf, - p$coverage, - p$secondaryCoverage, - p$signalCutoff, - p$minAbsCorr, - p$methodArgs, - p$verbose, + addSusieInf, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + verbose, label = glue("GWAS (study='{st}', region='{blockId}')"), af = .fmAfByVar(gr, zn$variantIds), nVar = zn$nVar, - fullFit = p$fullFit, - fullFitAlphaOnly = p$fullFitAlphaOnly, - includeAllCs = p$includeAllCs, - serFallback = p$serFallback, - rFinite = p$rFiniteResolved, - rMismatch = p$rMismatch, - rssControl = p$rssControl, - keepFullFit = p$keepFullFit + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + serFallback = serFallback, + rFinite = rFiniteResolved, + rMismatch = rMismatch, + rssControl = rssControl, + keepFullFit = keepFullFit ) map(names(ents), .fmGwasRowFor, st = st, blockId = blockId, ents = ents) } @@ -890,12 +912,37 @@ setGeneric("fineMappingPipeline", function(data, ...) { # tokens, or an empty set when the region was screened out. Returns # list(rows, skipped) so the caller sums the skip flags functionally. # @noRd -.fmGwasEntryRows <- function(i, p) { - st <- p$studyCol[[i]] - gr <- .collectionEntry(p$data, i) - skip <- .fmEntrySkipInfo(p$data, i) +.fmGwasEntryRows <- function( + i, + data, + studyCol, + tokens, + ldSketch, + verbose, + fineMappingResult, + mafCutoff, + macCutoff, + imissCutoff, + addSusieInf, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + fullFit, + fullFitAlphaOnly, + includeAllCs, + serFallback, + rFiniteResolved, + rMismatch, + rssControl, + keepFullFit +) { + st <- studyCol[[i]] + gr <- .collectionEntry(data, i) + skip <- .fmEntrySkipInfo(data, i) if (isTRUE(skip$skipped)) { - if (p$verbose >= 1) { + if (verbose >= 1) { reason <- skip$reason msg <- glue( "fineMappingPipeline(GwasSumStats): study='{st}' region ", @@ -908,11 +955,21 @@ setGeneric("fineMappingPipeline", function(data, ...) { zn <- .fmExtractZn( gr, glue("fineMappingPipeline(GwasSumStats): study='{st}'"), - ldSketch = p$ldSketch, - cutoffs = .panelCutoffs(p) + ldSketch = ldSketch, + cutoffs = .panelCutoffs( + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ) ) blockId <- .fmGwasBlockId(gr) - lookups <- map(p$tokens, .fmGwasLookup, p = p, st = st, blockId = blockId) + lookups <- map( + tokens, + .fmGwasLookup, + fineMappingResult = fineMappingResult, + st = st, + blockId = blockId + ) cachedRows <- map( keep(lookups, .fmHasCached), .fmGwasRowFromLookup, @@ -923,28 +980,80 @@ setGeneric("fineMappingPipeline", function(data, ...) { if (length(toRun) == 0L) { return(list(rows = cachedRows, skipped = FALSE)) } - computed <- .fmGwasFitRows(p, gr, zn, st, blockId, toRun) + computed <- .fmGwasFitRows( + gr, + zn, + st, + blockId, + toRun, + ldSketch = ldSketch, + addSusieInf = addSusieInf, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + verbose = verbose, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + serFallback = serFallback, + rFiniteResolved = rFiniteResolved, + rMismatch = rMismatch, + rssControl = rssControl, + keepFullFit = keepFullFit + ) list(rows = c(cachedRows, computed), skipped = FALSE) } # Fit the still-to-run RSS tokens for one QtlSumStats entry and return one # row-record per fitted token. # @noRd -.fmRssFitRows <- function(p, i, st, ctx, tr, toRun) { - entry <- .collectionEntry(p$data, i) +.fmRssFitRows <- function( + i, + st, + ctx, + tr, + toRun, + data, + ldSketch, + addSusieInf, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + verbose, + fullFit, + fullFitAlphaOnly, + includeAllCs, + serFallback, + rFiniteResolved, + rMismatch, + rssControl, + keepFullFit, + mafCutoff, + macCutoff, + imissCutoff +) { + entry <- .collectionEntry(data, i) zn <- .fmExtractZn( entry, glue( "fineMappingPipeline(QtlSumStats): entry {i} (study='{st}', ", "context='{ctx}', trait='{tr}')" ), - ldSketch = p$ldSketch, - cutoffs = .panelCutoffs(p) + ldSketch = ldSketch, + cutoffs = .panelCutoffs( + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ) ) z <- zn$z names(z) <- zn$variantIds ldMat <- .ldFromSketch( - p$ldSketch, + ldSketch, zn$variantIds, label = "fineMappingPipeline" ) @@ -953,24 +1062,24 @@ setGeneric("fineMappingPipeline", function(data, ...) { ldMat, zn$n, toRun, - p$addSusieInf, - p$coverage, - p$secondaryCoverage, - p$signalCutoff, - p$minAbsCorr, - p$methodArgs, - p$verbose, + addSusieInf, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + verbose, label = glue("(study='{st}', context='{ctx}', trait='{tr}')"), af = .fmAfByVar(entry, zn$variantIds), nVar = zn$nVar, - fullFit = p$fullFit, - fullFitAlphaOnly = p$fullFitAlphaOnly, - includeAllCs = p$includeAllCs, - serFallback = p$serFallback, - rFinite = p$rFiniteResolved, - rMismatch = p$rMismatch, - rssControl = p$rssControl, - keepFullFit = p$keepFullFit + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + serFallback = serFallback, + rFinite = rFiniteResolved, + rMismatch = rMismatch, + rssControl = rssControl, + keepFullFit = keepFullFit ) map(names(ents), .fmQtlRowFor, st = st, ctx = ctx, tr = tr, ents = ents) } @@ -979,14 +1088,41 @@ setGeneric("fineMappingPipeline", function(data, ...) { # tokens remain to run and the entry was not screened out) freshly-fitted # tokens. Returns list(rows, skipped) so the caller sums the skip flags. # @noRd -.fmRssEntryRows <- function(i, p) { - st <- p$studyCol[i] - ctx <- p$contextCol[i] - tr <- p$traitCol[i] +.fmRssEntryRows <- function( + i, + studyCol, + contextCol, + traitCol, + univTokens, + fineMappingResult, + data, + ldSketch, + addSusieInf, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + verbose, + fullFit, + fullFitAlphaOnly, + includeAllCs, + serFallback, + rFiniteResolved, + rMismatch, + rssControl, + keepFullFit, + mafCutoff, + macCutoff, + imissCutoff +) { + st <- studyCol[i] + ctx <- contextCol[i] + tr <- traitCol[i] lookups <- map( - p$univTokens, + univTokens, .fmQtlLookup, - p = p, + fineMappingResult = fineMappingResult, st = st, ctx = ctx, tr = tr @@ -1002,9 +1138,9 @@ setGeneric("fineMappingPipeline", function(data, ...) { if (length(toRun) == 0L) { return(list(rows = cachedRows, skipped = FALSE)) } - skip <- .fmEntrySkipInfo(p$data, i) + skip <- .fmEntrySkipInfo(data, i) if (isTRUE(skip$skipped)) { - if (p$verbose >= 1) { + if (verbose >= 1) { reason <- skip$reason msg <- glue( "fineMappingPipeline(QtlSumStats): entry {i} ", @@ -1015,19 +1151,43 @@ setGeneric("fineMappingPipeline", function(data, ...) { } return(list(rows = cachedRows, skipped = TRUE)) } - computed <- .fmRssFitRows(p, i, st, ctx, tr, toRun) + computed <- .fmRssFitRows( + i, + st, + ctx, + tr, + toRun, + data = data, + ldSketch = ldSketch, + addSusieInf = addSusieInf, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + verbose = verbose, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + serFallback = serFallback, + rFiniteResolved = rFiniteResolved, + rMismatch = rMismatch, + rssControl = rssControl, + keepFullFit = keepFullFit, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ) list(rows = c(cachedRows, computed), skipped = FALSE) } # Concatenate two same-class FineMappingResult collections row-wise, carrying # forward every column (delegates to the generic `.rbindCollections`). # @noRd +#' @importFrom checkmate assertClass .rbindFineMappingResult <- function(a, b, ldSketch = NULL) { - if (!is(a, "FineMappingResultBase") || !is(b, "FineMappingResultBase")) { - abort( - ".rbindFineMappingResult expects two FineMappingResultBase inputs." - ) - } + assertClass(a, "FineMappingResultBase") + assertClass(b, "FineMappingResultBase") # Carry forward every column (blockId / joint* / ...) and reconcile the # collection-level slots via the shared combine; the concrete class # (QTL vs GWAS) is preserved and checked there. @@ -1121,6 +1281,7 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # sources whose entries already carry `af`). The branch mirrors the # `.fmResidGeno` call that built `X`: `region`-driven when a joint range is # given, else `traitId` + `cisWindow` for the cis window. +#' @importFrom rlang try_fetch .fmAfForX <- function( data, X, @@ -1134,7 +1295,7 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { if (!is(data, "QtlDataset")) { return(NULL) } - afAll <- tryCatch( + afAll <- try_fetch( if (is.null(region)) { getAf( data, @@ -1145,7 +1306,7 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { } else { getAf(data, region = region, samples = rownames(X)) }, - error = function(e) NULL + error = function(cnd) NULL ) if (is.null(afAll) || length(afAll) == 0L) { return(NULL) @@ -1564,15 +1725,15 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { metric <- scr$metric cutoff <- scr$cutoff if (metric == "absZ") { - z <- tryCatch(.marginalZ(Xs, ys), error = function(e) NULL) + z <- try_fetch(.marginalZ(Xs, ys), error = function(cnd) NULL) if (is.null(z)) { return(fallback) } return(any(abs(z) > cutoff, na.rm = TRUE)) } - fit <- tryCatch( + fit <- try_fetch( suppressMessages(susieR::susie(Xs, ys, L = 1L)), - error = function(e) NULL + error = function(cnd) NULL ) if (is.null(fit)) { return(fallback) @@ -1702,6 +1863,21 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # user did not set. User-supplied values always win over base, capability # defaults, and chain-derived args. Returns the merged list. # @noRd +# Split a merged argument list into the target's own formals plus a +# `methodArgs` remainder. Callers build one flat list (base args, capability +# defaults, user overrides); this routes each name to the right place, so a +# tool option reaches the tool and an unknown name errors at the call instead +# of vanishing. A target with no `methodArgs` formal gets the list unchanged. +# @noRd +.splitMethodArgs <- function(fn, args) { + fm <- names(formals(match.fun(fn))) + if (!is_in("methodArgs", fm)) { + return(args) + } + isFormal <- is_in(names(args), setdiff(fm, "methodArgs")) + c(args[isFormal], list(methodArgs = args[!isFormal])) +} + .fmMergeUserArgs <- function(baseArgs, token, userArgs = NULL) { if (is.null(userArgs)) { userArgs <- list() @@ -1714,10 +1890,10 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { } # Order matters: base < capability defaults < user overrides. if (length(capDefaults) > 0L) { - baseArgs <- modifyList(baseArgs, capDefaults) + baseArgs <- list_modify(baseArgs, !!!compact(capDefaults)) } if (length(userArgs) > 0L) { - baseArgs <- modifyList(baseArgs, userArgs) + baseArgs <- list_modify(baseArgs, !!!compact(userArgs)) } baseArgs } @@ -1756,7 +1932,7 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # pipScreenReason); # an entry may also be empty for other reasons. Returns list(skipped, reason). .fmEntrySkipInfo <- function(data, i) { - ea <- tryCatch(getQcInfo(data)$entryAudit[[i]], error = function(e) NULL) + ea <- try_fetch(getQcInfo(data)$entryAudit[[i]], error = function(cnd) NULL) screened <- isTRUE(ea$pipScreenSkipped) entry <- .collectionEntry(data, i) empty <- is.null(entry) || length(entry) == 0L @@ -1882,7 +2058,7 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { baseArgs$residual_variance <- mvPrior$residualVariance } mvArgs <- .fmMergeUserArgs(baseArgs, "mvsusie", userArgs) - fit <- exec(fitMvsusie, !!!mvArgs) + fit <- exec(fitMvsusie, !!!.splitMethodArgs(fitMvsusie, mvArgs)) W <- as.matrix(mvsusieWeights(mvsusieFit = fit)) if (is.null(rownames(W))) { rownames(W) <- colnames(Xtr) @@ -1898,7 +2074,7 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { "fsusie", userArgs ) - fit <- exec(fitFsusie, !!!fsArgs) + fit <- exec(fitFsusie, !!!.splitMethodArgs(fitFsusie, fsArgs)) W <- fsusieWeights(fsusieFit = fit, variantIds = colnames(Xtr)) as.matrix(W) } @@ -1926,7 +2102,7 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { } weights <- list() for (tk in tokens) { - weights[[.fmTwasMethodKey(tk)]] <- tryCatch( + weights[[.fmTwasMethodKey(tk)]] <- try_fetch( .fmFoldWeights( tk, Xtr, @@ -1936,9 +2112,9 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { pos, mvPriorThisFold ), - error = function(e) { + error = function(cnd) { if (verbose >= 1) { - eMsg <- conditionMessage(e) + eMsg <- conditionMessage(cnd) msg <- glue( " CV fold {j}, method {tk} failed: {eMsg}", .trim = FALSE @@ -2049,17 +2225,24 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # `rg` is NULL, else the explicit region. Shared by the univariate and PCA # dispatch paths. # @noRd -.fmResidGenoBlock <- function(p, ctx, traitId, rg, samples) { +.fmResidGenoBlock <- function( + ctx, + traitId, + rg, + samples, + data, + cisWindow +) { if (is.null(rg)) { .fmResidGeno( - p$data, + data, contexts = ctx, traitId = traitId, - cisWindow = p$cisWindow, + cisWindow = cisWindow, samples = samples ) } else { - .fmResidGeno(p$data, contexts = ctx, region = rg, samples = samples) + .fmResidGeno(data, contexts = ctx, region = rg, samples = samples) } } @@ -2083,28 +2266,49 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # callers pass only the block-specific arguments (design, response, tokens, # addSusieInf, context label, trait/PC label, allele frequencies). # @noRd -.fmFitXBlockP <- function(p, X, y, tokens, addSusieInf, ctx, label, afVec) { +.fmFitXBlockP <- function( + X, + y, + tokens, + addSusieInf, + ctx, + label, + afVec, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + verbose, + cvFolds, + cvThreads, + samplePartition, + seed, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { .fmFitXBlock( X, y, tokens, addSusieInf, - p$coverage, - p$secondaryCoverage, - p$signalCutoff, - p$minAbsCorr, - p$methodArgs, - p$verbose, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + verbose, ctx, label, - cvFolds = p$cvFolds, - cvThreads = p$cvThreads, - samplePartition = p$samplePartition, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, af = afVec, - fullFit = p$fullFit, - fullFitAlphaOnly = p$fullFitAlphaOnly, - includeAllCs = p$includeAllCs, - seed = p$seed + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + seed = seed ) } @@ -2112,8 +2316,38 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # Y, SER pre-screen, then .fmFitXBlock. Errors when too few shared samples # (a hard data problem), returns list() when the window screens out. # @noRd -.fmUnivBlockFit <- function(rg, p, ctx, tid, Y, toRun) { - X <- .fmResidGenoBlock(p, ctx, tid, rg, rownames(Y)) +.fmUnivBlockFit <- function( + rg, + ctx, + tid, + Y, + toRun, + data, + cisWindow, + screen, + addSusieInf, + verbose, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + cvFolds, + cvThreads, + samplePartition, + seed, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { + X <- .fmResidGenoBlock( + ctx, + tid, + rg, + rownames(Y), + data = data, + cisWindow = cisWindow + ) common <- intersect(rownames(X), rownames(Y)) if (length(common) < 2L) { msg <- glue( @@ -2129,8 +2363,8 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { } else { y <- drop(y) } - if (!.fmSerScreen(X, y, p$screen)) { - if (p$verbose >= 1) { + if (!.fmSerScreen(X, y, screen)) { + if (verbose >= 1) { msg <- glue( "Skipping (context='{ctx}', trait='{tid}'): SER ", "pre-screen found no signal above the cutoff." @@ -2140,24 +2374,77 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { return(list()) } afVec <- .fmAfForX( - p$data, + data, X, traitId = tid, region = rg, - cisWindow = p$cisWindow + cisWindow = cisWindow + ) + .fmFitXBlockP( + X, + y, + toRun, + addSusieInf, + ctx, + tid, + afVec, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + verbose = verbose, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs ) - .fmFitXBlockP(p, X, y, toRun, p$addSusieInf, ctx, tid, afVec) } # All univariate row-records for one (context, trait): cache hits, then (when # tokens remain) per-window fits merged per token. # @noRd -.fmUnivTraitRows <- function(tid, p, ctx) { - lookups <- map(p$univTokens, .fmUnivLookup, p = p, ctx = ctx, tid = tid) +.fmUnivTraitRows <- function( + tid, + ctx, + data, + study, + univTokens, + xRegions, + naAction, + fineMappingResult, + cisWindow, + screen, + addSusieInf, + verbose, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + cvFolds, + cvThreads, + samplePartition, + seed, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { + lookups <- map( + univTokens, + .fmUnivLookup, + fineMappingResult = fineMappingResult, + study = study, + ctx = ctx, + tid = tid + ) cachedRows <- map( keep(lookups, .fmHasCached), .fmUnivCachedRow, - p = p, + study = study, ctx = ctx, tid = tid ) @@ -2166,21 +2453,37 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { return(cachedRows) } Y <- .fmResidPheno( - p$data, + data, contexts = ctx, traitId = tid, - naAction = p$naAction + naAction = naAction ) blockEntries <- map( - p$xRegions, + xRegions, .fmUnivBlockFit, - p = p, ctx = ctx, tid = tid, + data = data, + cisWindow = cisWindow, + screen = screen, + addSusieInf = addSusieInf, + verbose = verbose, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, Y = Y, toRun = toRun ) - computed <- .fmMergeTokenRows(p$study, ctx, tid, toRun, blockEntries) + computed <- .fmMergeTokenRows(study, ctx, tid, toRun, blockEntries) c(cachedRows, computed) } @@ -2188,69 +2491,187 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # PC scores, SER pre-screen, then univariate-susie .fmFitXBlock. Returns # list() when too few shared samples or the window screens out (soft skip). # @noRd -.fmPcaBlockFit <- function(rg, p, ctx, traits, pcName, pcY, samples) { - X <- .fmResidGenoBlock(p, ctx, traits, rg, samples) +.fmPcaBlockFit <- function( + rg, + ctx, + traits, + pcName, + pcY, + samples, + data, + cisWindow, + screen, + addSusieInf, + verbose, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + cvFolds, + cvThreads, + samplePartition, + seed, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { + X <- .fmResidGenoBlock( + ctx, + traits, + rg, + samples, + data = data, + cisWindow = cisWindow + ) common <- intersect(rownames(X), names(pcY)) if (length(common) < 2L) { return(list()) } Xb <- X[common, , drop = FALSE] - if (!.fmSerScreen(Xb, pcY[common], p$screen)) { + if (!.fmSerScreen(Xb, pcY[common], screen)) { return(list()) } afVec <- .fmAfForX( - p$data, + data, Xb, traitId = traits, region = rg, - cisWindow = p$cisWindow + cisWindow = cisWindow + ) + .fmFitXBlockP( + Xb, + pcY[common], + "susie", + FALSE, + ctx, + pcName, + afVec, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + verbose = verbose, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs ) - .fmFitXBlockP(p, Xb, pcY[common], "susie", FALSE, ctx, pcName, afVec) } # Row-records for one PC pseudo-trait: a cache hit, else per-window fits merged # into the single "susie" token (trait = the PC name). # @noRd -.fmPcaScoreRows <- function(pcName, p, ctx, traits, scores) { - cached <- .fmCacheLookup(p$fineMappingResult, p$study, ctx, pcName, "susie") +.fmPcaScoreRows <- function( + pcName, + ctx, + traits, + scores, + fineMappingResult, + study, + xRegions, + data, + cisWindow, + screen, + addSusieInf, + verbose, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + cvFolds, + cvThreads, + samplePartition, + seed, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { + cached <- .fmCacheLookup(fineMappingResult, study, ctx, pcName, "susie") if (!is.null(cached)) { - return(list(.fmQtlRow(p$study, ctx, pcName, "susie", cached))) + return(list(.fmQtlRow(study, ctx, pcName, "susie", cached))) } pcY <- scores[, pcName] samples <- rownames(scores) blockEntries <- map( - p$xRegions, + xRegions, .fmPcaBlockFit, - p = p, ctx = ctx, traits = traits, pcName = pcName, pcY = pcY, - samples = samples + samples = samples, + data = data, + cisWindow = cisWindow, + screen = screen, + addSusieInf = addSusieInf, + verbose = verbose, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs ) - .fmMergeTokenRows(p$study, ctx, pcName, "susie", blockEntries) + .fmMergeTokenRows(study, ctx, pcName, "susie", blockEntries) } # All usePCA row-records for one context: PCA-reduce the multi-trait phenotype # and fine-map each top PC. A single-trait context (or one with no usable PC # scores) contributes nothing. # @noRd -.fmPcaContextRows <- function(ctx, p) { - traits <- p$perCtxTraits[[ctx]] +.fmPcaContextRows <- function( + ctx, + data, + study, + perCtxTraits, + nPCs, + naAction, + fineMappingResult, + xRegions, + cisWindow, + screen, + addSusieInf, + verbose, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + cvFolds, + cvThreads, + samplePartition, + seed, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { + traits <- perCtxTraits[[ctx]] if (length(traits) < 2L) { return(list()) } Yctx <- .fmResidPheno( - p$data, + data, contexts = ctx, traitId = traits, - naAction = p$naAction + naAction = naAction ) - scores <- .fmTopPcScores(Yctx, p$nPCs) + scores <- .fmTopPcScores(Yctx, nPCs) if (is.null(scores)) { return(list()) } - if (p$verbose >= 1) { + if (verbose >= 1) { nPc <- ncol(scores) nTr <- length(traits) msg <- glue( @@ -2262,10 +2683,29 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { list_flatten(map( colnames(scores), .fmPcaScoreRows, - p = p, ctx = ctx, traits = traits, - scores = scores + scores = scores, + fineMappingResult = fineMappingResult, + study = study, + xRegions = xRegions, + data = data, + cisWindow = cisWindow, + screen = screen, + addSusieInf = addSusieInf, + verbose = verbose, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs )) } @@ -2276,32 +2716,57 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # by the explicit-jointSpecification path and the auto-detected multivariate # path; only the spec and token arguments differ between them. # @noRd -.fmQdsJointDispatch <- function(p, jointSpec, tokens, methodArgs) { +.fmQdsJointDispatch <- function( + jointSpec, + tokens, + methodArgs, + data, + contexts, + traitId, + cisWindow, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + verbose, + xRegions, + twasWeights, + dataDrivenPriorWeightsCutoff, + cvFolds, + cvThreads, + samplePartition, + screen, + fineMappingResult, + fullFit, + fullFitAlphaOnly, + includeAllCs, + seed +) { .fmDispatchJointSpecsQtlDataset( jointSpec, - p$data, + data, tokens, - p$contexts, - p$traitId, - p$cisWindow, - p$coverage, - p$secondaryCoverage, - p$signalCutoff, - p$minAbsCorr, - p$verbose, + contexts, + traitId, + cisWindow, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + verbose, methodArgs = methodArgs, - xRegions = p$xRegions, - twasWeights = p$twasWeights, - dataDrivenPriorWeightsCutoff = p$dataDrivenPriorWeightsCutoff, - cvFolds = p$cvFolds, - cvThreads = p$cvThreads, - samplePartition = p$samplePartition, - pipCutoffToSkip = p$screen, - fineMappingResult = p$fineMappingResult, - fullFit = p$fullFit, - fullFitAlphaOnly = p$fullFitAlphaOnly, - includeAllCs = p$includeAllCs, - seed = p$seed + xRegions = xRegions, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + pipCutoffToSkip = screen, + fineMappingResult = fineMappingResult, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + seed = seed ) } @@ -2309,24 +2774,40 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # copy of the dataset, and derive the X windows. Rejects the region + cisWindow # combination. Returns `p` extended with data / screen / xRegions. # @noRd -.fmQdsResolveInputs <- function(p) { +.fmQdsResolveInputs <- function( + data, + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip, + mafCutoff, + macCutoff, + xvarCutoff, + imissCutoff, + keepIndel, + keepSamples, + keepVariants, + region, + cisWindow, + jointRegions +) { screen <- .resolveScreenMetric( - p$pipCutoffToSkip, - p$absZCutoffToSkip, - p$bfCutoffToSkip, - p$logBfCutoffToSkip + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip ) data <- .qtlApplyFilterOverrides( - p$data, - p$mafCutoff, - p$macCutoff, - p$xvarCutoff, - p$imissCutoff, - p$keepIndel, - p$keepSamples, - p$keepVariants - ) - if (!is.null(p$region) && !is.null(p$cisWindow)) { + data, + mafCutoff, + macCutoff, + xvarCutoff, + imissCutoff, + keepIndel, + keepSamples, + keepVariants + ) + if (!is.null(region) && !is.null(cisWindow)) { msg <- glue( "fineMappingPipeline(QtlDataset): specify either `region` or ", "`cisWindow`, not both. `cisWindow` expands each trait's own ", @@ -2334,11 +2815,10 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { ) abort(msg) } - list_modify( - p, + list( data = data, screen = screen, - xRegions = .makeXRegions(p$region, p$jointRegions) + xRegions = .makeXRegions(region, jointRegions) ) } @@ -2347,9 +2827,35 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # token set). `exhaustedByJoint` flags the case where the explicit spec # consumed every method. # @noRd -.fmQdsResolveTokens <- function(p) { - parsedJointSpec <- parseJointSpecification(p$jointSpecification, p$data) - norm <- .fmNormalizeMethods(p$methods, L = p$L, Lgreedy = p$Lgreedy) +.fmQdsResolveTokens <- function( + jointSpecification, + methods, + L, + Lgreedy, + data, + contexts, + traitId, + cisWindow, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + verbose, + xRegions, + twasWeights, + dataDrivenPriorWeightsCutoff, + cvFolds, + cvThreads, + samplePartition, + screen, + fineMappingResult, + fullFit, + fullFitAlphaOnly, + includeAllCs, + seed +) { + parsedJointSpec <- parseJointSpecification(jointSpecification, data) + norm <- .fmNormalizeMethods(methods, L = L, Lgreedy = Lgreedy) tokens <- norm$tokens methodArgs <- norm$methodArgs .fmCheckMethodCapabilities(tokens, "QtlDataset") @@ -2357,10 +2863,30 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { hadJointSpec <- length(parsedJointSpec) > 0L if (hadJointSpec) { jointResult <- .fmQdsJointDispatch( - p, parsedJointSpec, intersect(tokens, c("mvsusie", "fsusie")), - methodArgs + methodArgs, + data = data, + contexts = contexts, + traitId = traitId, + cisWindow = cisWindow, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + verbose = verbose, + xRegions = xRegions, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + screen = screen, + fineMappingResult = fineMappingResult, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + seed = seed ) tokens <- setdiff(tokens, c("mvsusie", "fsusie")) methodArgs <- methodArgs[tokens] @@ -2376,14 +2902,14 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # Trait ids available in one context, intersected with the requested traitId or # overlapping the requested region (mirrors twasWeightsPipeline). # @noRd -.fmQdsTraitsForContext <- function(ctx, p) { - se <- getPhenotypes(p$data, contexts = ctx) +.fmQdsTraitsForContext <- function(ctx, data, traitId, region) { + se <- getPhenotypes(data, contexts = ctx) ids <- rownames(se) - if (!is.null(p$traitId)) { - ids <- intersect(ids, p$traitId) - } else if (!is.null(p$region)) { + if (!is.null(traitId)) { + ids <- intersect(ids, traitId) + } else if (!is.null(region)) { rr <- SummarizedExperiment::rowRanges(se) - ids <- ids[IRanges::overlapsAny(rr, p$region)] + ids <- ids[IRanges::overlapsAny(rr, region)] } ids } @@ -2392,12 +2918,12 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # the per-context trait lists. Returns `p` extended with study / useCtx / # perCtxTraits / nCtx / nTraits. # @noRd -.fmQdsResolveContexts <- function(p) { - allCtx <- getContexts(p$data) - useCtx <- if (is.null(p$contexts)) { +.fmQdsResolveContexts <- function(data, contexts, traitId, region) { + allCtx <- getContexts(data) + useCtx <- if (is.null(contexts)) { allCtx } else { - bad <- setdiff(p$contexts, allCtx) + bad <- setdiff(contexts, allCtx) if (length(bad) > 0L) { badStr <- str_flatten(bad, ", ") msg <- glue( @@ -2406,17 +2932,22 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { ) abort(msg) } - p$contexts + contexts } - perCtxTraits <- map(useCtx, .fmQdsTraitsForContext, p = p) + perCtxTraits <- map( + useCtx, + .fmQdsTraitsForContext, + data = data, + traitId = traitId, + region = region + ) names(perCtxTraits) <- useCtx allTraits <- unique(list_c(perCtxTraits)) if (length(allTraits) == 0L) { abort("fineMappingPipeline(QtlDataset): no traits selected.") } - list_modify( - p, - study = getStudy(p$data), + list( + study = getStudy(data), useCtx = useCtx, perCtxTraits = perCtxTraits, nCtx = length(useCtx), @@ -2428,27 +2959,26 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # multivariate requirements (mvsusie needs multi-trait OR multi-context; fsusie # needs multi-trait per context). Returns `p` extended with the three sets. # @noRd -.fmQdsSplitTokens <- function(p) { - univTokens <- p$tokens[!is_in(p$tokens, c("mvsusie", "fsusie"))] - mvTokens <- p$tokens[p$tokens == "mvsusie"] - fsTokens <- p$tokens[p$tokens == "fsusie"] - if (length(mvTokens) > 0L && p$nCtx < 2L && p$nTraits < 2L) { +.fmQdsSplitTokens <- function(tokens, nCtx, nTraits) { + univTokens <- tokens[!is_in(tokens, c("mvsusie", "fsusie"))] + mvTokens <- tokens[tokens == "mvsusie"] + fsTokens <- tokens[tokens == "fsusie"] + if (length(mvTokens) > 0L && nCtx < 2L && nTraits < 2L) { msg <- glue( "fineMappingPipeline(QtlDataset): mvsusie requires multi-trait ", - "or multi-context input (got {p$nTraits} trait(s) x ", - "{p$nCtx} context(s))." + "or multi-context input (got {nTraits} trait(s) x ", + "{nCtx} context(s))." ) abort(msg) } - if (length(fsTokens) > 0L && p$nTraits < 2L) { + if (length(fsTokens) > 0L && nTraits < 2L) { msg <- glue( "fineMappingPipeline(QtlDataset): fsusie requires multi-trait ", - "input within a context (got {p$nTraits} trait(s))." + "input within a context (got {nTraits} trait(s))." ) abort(msg) } - list_modify( - p, + list( univTokens = univTokens, mvTokens = mvTokens, fsTokens = fsTokens @@ -2458,14 +2988,93 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # Univariate + usePCA dispatch: each (context, trait) -> merged-per-token # row-records; each multi-trait context's top PCs -> pseudo-trait rows. # @noRd -.fmQdsDispatchRows <- function(p) { - univRows <- if (length(p$univTokens) > 0L) { - list_flatten(map(p$useCtx, .fmUnivContextRows, p = p)) +.fmQdsDispatchRows <- function( + addSusieInf, + cisWindow, + coverage, + cvFolds, + cvThreads, + data, + fineMappingResult, + fullFit, + fullFitAlphaOnly, + includeAllCs, + methodArgs, + minAbsCorr, + nPCs, + naAction, + perCtxTraits, + samplePartition, + screen, + secondaryCoverage, + seed, + signalCutoff, + study, + univTokens, + useCtx, + usePCA, + verbose, + xRegions +) { + univRows <- if (length(univTokens) > 0L) { + list_flatten(map( + useCtx, + .fmUnivContextRows, + data = data, + study = study, + univTokens = univTokens, + xRegions = xRegions, + naAction = naAction, + fineMappingResult = fineMappingResult, + cisWindow = cisWindow, + screen = screen, + addSusieInf = addSusieInf, + verbose = verbose, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + perCtxTraits = perCtxTraits + )) } else { list() } - pcaRows <- if (isTRUE(p$usePCA)) { - list_flatten(map(p$useCtx, .fmPcaContextRows, p = p)) + pcaRows <- if (isTRUE(usePCA)) { + list_flatten(map( + useCtx, + .fmPcaContextRows, + data = data, + study = study, + xRegions = xRegions, + naAction = naAction, + fineMappingResult = fineMappingResult, + cisWindow = cisWindow, + screen = screen, + addSusieInf = addSusieInf, + verbose = verbose, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + perCtxTraits = perCtxTraits, + nPCs = nPCs + )) } else { list() } @@ -2476,22 +3085,70 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # mvsusie / fsusie WITHOUT an explicit jointSpecification, merged with any # explicit-spec result already in `p$jointResult`. # @noRd -.fmQdsAutoJoint <- function(p) { - if (length(p$mvTokens) == 0L && length(p$fsTokens) == 0L) { - return(p$jointResult) +.fmQdsAutoJoint <- function( + data, + contexts, + traitId, + cisWindow, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + verbose, + xRegions, + twasWeights, + dataDrivenPriorWeightsCutoff, + cvFolds, + cvThreads, + samplePartition, + screen, + fineMappingResult, + fullFit, + fullFitAlphaOnly, + includeAllCs, + seed, + mvTokens, + fsTokens, + jointResult, + methodArgs, + nCtx, + nTraits +) { + if (length(mvTokens) == 0L && length(fsTokens) == 0L) { + return(jointResult) } autoJoint <- .fmQdsJointDispatch( - p, - .fmSynthesizeJointSpec(p$nCtx, p$nTraits), - c(p$mvTokens, p$fsTokens), - p$methodArgs + .fmSynthesizeJointSpec(nCtx, nTraits), + c(mvTokens, fsTokens), + methodArgs, + data = data, + contexts = contexts, + traitId = traitId, + cisWindow = cisWindow, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + verbose = verbose, + xRegions = xRegions, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + screen = screen, + fineMappingResult = fineMappingResult, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + seed = seed ) - if (is.null(p$jointResult)) { + if (is.null(jointResult)) { autoJoint } else if (is.null(autoJoint)) { - p$jointResult + jointResult } else { - .rbindFineMappingResult(p$jointResult, autoJoint, ldSketch = NULL) + .rbindFineMappingResult(jointResult, autoJoint, ldSketch = NULL) } } @@ -2499,7 +3156,7 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # trait-anchored cis span) combined with the joint result. Errors only when # neither path produced anything. # @noRd -.fmQdsAssemble <- function(p, rows, jointResult) { +.fmQdsAssemble <- function(data, rows, jointResult) { rowContext <- map_chr(rows, "context") rowTrait <- map_chr(rows, "trait") perTupleResult <- if (length(rows) > 0L) { @@ -2509,9 +3166,9 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { rowTrait, map_chr(rows, "method"), map(rows, "entry"), - traitPos = tryCatch( - .anchorVector(p$data, rowContext, rowTrait, "traitPos"), - error = function(e) NULL + traitPos = try_fetch( + .anchorVector(data, rowContext, rowTrait, "traitPos"), + error = function(cnd) NULL ), ldSketch = NULL ) @@ -2540,14 +3197,101 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { # .fmFitXBlock / .fmFitRssBlock (which recompute .fmResolveSusieChain), so no # chain config is threaded here. # @noRd -.fmPipelineQtlDataset <- function(p) { - naAction <- p$naAction - p$naAction <- arg_match(naAction, c("drop", "impute")) - if (!is.null(p$seed)) { - withr::local_seed(as.integer(p$seed)) +.fmPipelineQtlDataset <- function( + data, + methods, + contexts, + traitId, + region, + cisWindow, + mafCutoff, + macCutoff, + xvarCutoff, + imissCutoff, + keepIndel, + keepSamples, + keepVariants, + jointRegions, + jointSpecification, + addSusieInf, + L, + Lgreedy, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + fineMappingResult, + twasWeights, + dataDrivenPriorWeightsCutoff, + verbose, + cvFolds, + cvThreads, + samplePartition, + seed, + naAction, + usePCA, + nPCs, + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { + naAction <- arg_match(naAction, c("drop", "impute")) + if (!is.null(seed)) { + withr::local_seed(as.integer(seed)) } - p <- .fmQdsResolveInputs(p) - rt <- .fmQdsResolveTokens(p) + # Each stage returns only the values it derives; nothing is grafted onto a + # captured environment. + resolved <- .fmQdsResolveInputs( + data = data, + pipCutoffToSkip = pipCutoffToSkip, + absZCutoffToSkip = absZCutoffToSkip, + bfCutoffToSkip = bfCutoffToSkip, + logBfCutoffToSkip = logBfCutoffToSkip, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + xvarCutoff = xvarCutoff, + imissCutoff = imissCutoff, + keepIndel = keepIndel, + keepSamples = keepSamples, + keepVariants = keepVariants, + region = region, + cisWindow = cisWindow, + jointRegions = jointRegions + ) + data <- resolved$data + screen <- resolved$screen + xRegions <- resolved$xRegions + rt <- .fmQdsResolveTokens( + jointSpecification = jointSpecification, + methods = methods, + L = L, + Lgreedy = Lgreedy, + data = data, + contexts = contexts, + traitId = traitId, + cisWindow = cisWindow, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + verbose = verbose, + xRegions = xRegions, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + screen = screen, + fineMappingResult = fineMappingResult, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + seed = seed + ) if (rt$exhaustedByJoint) { if (is.null(rt$jointResult)) { msg <- glue( @@ -2559,15 +3303,85 @@ combineFineMappingResults <- function(..., ldSketch = NULL) { } return(rt$jointResult) } - p <- list_modify( - p, - tokens = rt$tokens, - methodArgs = rt$methodArgs, - jointResult = rt$jointResult + tokens <- rt$tokens + methodArgs <- rt$methodArgs + jointResult <- rt$jointResult + ctxInfo <- .fmQdsResolveContexts( + data = data, + contexts = contexts, + traitId = traitId, + region = region + ) + study <- ctxInfo$study + useCtx <- ctxInfo$useCtx + perCtxTraits <- ctxInfo$perCtxTraits + nCtx <- ctxInfo$nCtx + nTraits <- ctxInfo$nTraits + split <- .fmQdsSplitTokens(tokens, nCtx, nTraits) + univTokens <- split$univTokens + mvTokens <- split$mvTokens + fsTokens <- split$fsTokens + rows <- .fmQdsDispatchRows( + addSusieInf = addSusieInf, + cisWindow = cisWindow, + coverage = coverage, + cvFolds = cvFolds, + cvThreads = cvThreads, + data = data, + fineMappingResult = fineMappingResult, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + methodArgs = methodArgs, + minAbsCorr = minAbsCorr, + nPCs = nPCs, + naAction = naAction, + perCtxTraits = perCtxTraits, + samplePartition = samplePartition, + screen = screen, + secondaryCoverage = secondaryCoverage, + seed = seed, + signalCutoff = signalCutoff, + study = study, + univTokens = univTokens, + useCtx = useCtx, + usePCA = usePCA, + verbose = verbose, + xRegions = xRegions + ) + .fmQdsAssemble( + data, + rows, + .fmQdsAutoJoint( + data = data, + contexts = contexts, + traitId = traitId, + cisWindow = cisWindow, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + verbose = verbose, + xRegions = xRegions, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + screen = screen, + fineMappingResult = fineMappingResult, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + seed = seed, + mvTokens = mvTokens, + fsTokens = fsTokens, + jointResult = jointResult, + methodArgs = methodArgs, + nCtx = nCtx, + nTraits = nTraits + ) ) - p <- .fmQdsSplitTokens(.fmQdsResolveContexts(p)) - rows <- .fmQdsDispatchRows(p) - .fmQdsAssemble(p, rows, .fmQdsAutoJoint(p)) } #' @rdname fineMappingPipeline @@ -2627,7 +3441,48 @@ setMethod( residualizeGenotypeCovariates = TRUE, ... ) { - .fmPipelineQtlDataset(as.list(environment())) + .fmPipelineQtlDataset( + data = data, + methods = methods, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + xvarCutoff = xvarCutoff, + imissCutoff = imissCutoff, + keepIndel = keepIndel, + keepSamples = keepSamples, + keepVariants = keepVariants, + jointRegions = jointRegions, + jointSpecification = jointSpecification, + addSusieInf = addSusieInf, + L = L, + Lgreedy = Lgreedy, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + verbose = verbose, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + naAction = naAction, + usePCA = usePCA, + nPCs = nPCs, + pipCutoffToSkip = pipCutoffToSkip, + absZCutoffToSkip = absZCutoffToSkip, + bfCutoffToSkip = bfCutoffToSkip, + logBfCutoffToSkip = logBfCutoffToSkip, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs + ) } ) @@ -2710,32 +3565,47 @@ setMethod( # from the per-tuple recursion. Returns the still-pending tokens, the forwarded # `methods` (kwargs-preserving), the joint result, and `exhaustedByJoint`. # @noRd -.fmMsResolveTokens <- function(p) { - parsedJointSpec <- parseJointSpecification(p$jointSpecification, p$data) - norm <- .fmNormalizeMethods(p$methods) +.fmMsResolveTokens <- function( + data, + methods, + contexts, + traitId, + cisWindow, + xRegions, + jointSpecification, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + twasWeights, + dataDrivenPriorWeightsCutoff, + verbose +) { + parsedJointSpec <- parseJointSpecification(jointSpecification, data) + norm <- .fmNormalizeMethods(methods) tokens <- norm$tokens methodArgs <- norm$methodArgs .fmCheckMethodCapabilities(tokens, "MultiStudyQtlDataset") jointResult <- NULL - methods <- p$methods + methods <- methods hadJointSpec <- length(parsedJointSpec) > 0L if (hadJointSpec) { jointResult <- .fmDispatchJointSpecsMultiStudy( parsedJointSpec, - p$data, + data, intersect(tokens, c("mvsusie", "fsusie")), - p$contexts, - p$traitId, - p$cisWindow, - p$coverage, - p$secondaryCoverage, - p$signalCutoff, - p$minAbsCorr, - p$verbose, + contexts, + traitId, + cisWindow, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + verbose, methodArgs = methodArgs, - xRegions = p$xRegions, - twasWeights = p$twasWeights, - dataDrivenPriorWeightsCutoff = p$dataDrivenPriorWeightsCutoff + xRegions = xRegions, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff ) tokens <- setdiff(tokens, c("mvsusie", "fsusie")) methodArgs <- methodArgs[tokens] @@ -2753,31 +3623,55 @@ setMethod( # individual-capable methods route to the per-study QtlDatasets, sumstat-capable # methods (incl. the sumstat-only `ser`) to the embedded QtlSumStats. # @noRd -.fmMsConfig <- function(p) { +.fmMsConfig <- function( + methods, + contexts, + traitId, + region, + cisWindow, + jointRegions, + addSusieInf, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + fineMappingResult, + verbose, + cvFolds, + cvThreads, + samplePartition, + seed, + naAction, + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip, + dotArgs +) { list( - methods = p$methods, - contexts = p$contexts, - traitId = p$traitId, - region = p$region, - cisWindow = p$cisWindow, - jointRegions = p$jointRegions, - addSusieInf = p$addSusieInf, - coverage = p$coverage, - secondaryCoverage = p$secondaryCoverage, - signalCutoff = p$signalCutoff, - minAbsCorr = p$minAbsCorr, - fineMappingResult = p$fineMappingResult, - cvFolds = p$cvFolds, - cvThreads = p$cvThreads, - samplePartition = p$samplePartition, - pipCutoffToSkip = p$pipCutoffToSkip, - absZCutoffToSkip = p$absZCutoffToSkip, - bfCutoffToSkip = p$bfCutoffToSkip, - logBfCutoffToSkip = p$logBfCutoffToSkip, - seed = p$seed, - naAction = p$naAction, - verbose = p$verbose, - dotArgs = p$dotArgs + methods = methods, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + jointRegions = jointRegions, + addSusieInf = addSusieInf, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + fineMappingResult = fineMappingResult, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + pipCutoffToSkip = pipCutoffToSkip, + absZCutoffToSkip = absZCutoffToSkip, + bfCutoffToSkip = bfCutoffToSkip, + logBfCutoffToSkip = logBfCutoffToSkip, + seed = seed, + naAction = naAction, + verbose = verbose, + dotArgs = dotArgs ) } @@ -2786,22 +3680,60 @@ setMethod( # each remaining method to the components it supports via the shared # multi-study driver. # @noRd -.fmPipelineMultiStudy <- function(p) { - naAction <- p$naAction +.fmPipelineMultiStudy <- function( + data, + methods, + contexts, + traitId, + region, + cisWindow, + jointRegions, + jointSpecification, + addSusieInf, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + fineMappingResult, + twasWeights, + dataDrivenPriorWeightsCutoff, + verbose, + cvFolds, + cvThreads, + samplePartition, + seed, + naAction, + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip, + dotArgs +) { naAction <- arg_match(naAction, c("drop", "impute")) - if (!is.null(p$region) && !is.null(p$cisWindow)) { + if (!is.null(region) && !is.null(cisWindow)) { msg <- glue( "fineMappingPipeline(MultiStudyQtlDataset): specify either ", "`region` or `cisWindow`, not both." ) abort(msg) } - p <- list_modify( - p, - naAction = naAction, - xRegions = .makeXRegions(p$region, p$jointRegions) + xRegions <- .makeXRegions(region, jointRegions) + rt <- .fmMsResolveTokens( + data = data, + methods = methods, + contexts = contexts, + traitId = traitId, + cisWindow = cisWindow, + xRegions = xRegions, + jointSpecification = jointSpecification, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + verbose = verbose ) - rt <- .fmMsResolveTokens(p) if (rt$exhaustedByJoint) { if (is.null(rt$jointResult)) { msg <- glue( @@ -2813,13 +3745,37 @@ setMethod( } return(rt$jointResult) } - p <- list_modify(p, methods = rt$methods) + methods <- rt$methods .multiStudyPipelineDriver( - p$data, + data, rt$jointResult, .fmPerStudy, .fmSumStats, - .fmMsConfig(p), + .fmMsConfig( + methods = methods, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + jointRegions = jointRegions, + addSusieInf = addSusieInf, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + fineMappingResult = fineMappingResult, + verbose = verbose, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + naAction = naAction, + pipCutoffToSkip = pipCutoffToSkip, + absZCutoffToSkip = absZCutoffToSkip, + bfCutoffToSkip = bfCutoffToSkip, + logBfCutoffToSkip = logBfCutoffToSkip, + dotArgs = dotArgs + ), .rbindFineMappingResult, QtlFineMappingResult, "fineMappingPipeline" @@ -2869,9 +3825,35 @@ setMethod( residualizeGenotypeCovariates = TRUE, ... ) { - p <- as.list(environment()) - p$dotArgs <- list(...) - .fmPipelineMultiStudy(p) + .fmPipelineMultiStudy( + data = data, + methods = methods, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + jointRegions = jointRegions, + jointSpecification = jointSpecification, + addSusieInf = addSusieInf, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + verbose = verbose, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + naAction = naAction, + pipCutoffToSkip = pipCutoffToSkip, + absZCutoffToSkip = absZCutoffToSkip, + bfCutoffToSkip = bfCutoffToSkip, + logBfCutoffToSkip = logBfCutoffToSkip, + dotArgs = list(...) + ) } ) @@ -2886,9 +3868,29 @@ setMethod( # where an explicit spec consumed every method, so the caller returns the joint # result directly (or errors when it produced nothing). # @noRd -.fmQssResolveTokens <- function(p) { - parsedJointSpec <- parseJointSpecification(p$jointSpecification, p$data) - norm <- .fmNormalizeMethods(p$methods) +.fmQssResolveTokens <- function( + data, + methods, + contexts, + traitId, + jointSpecification, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + fineMappingResult, + twasWeights, + dataDrivenPriorWeightsCutoff, + verbose, + fullFit, + fullFitAlphaOnly, + includeAllCs, + mafCutoff, + macCutoff, + imissCutoff +) { + parsedJointSpec <- parseJointSpecification(jointSpecification, data) + norm <- .fmNormalizeMethods(methods) tokens <- norm$tokens methodArgs <- norm$methodArgs .fmCheckMethodCapabilities(tokens, "QtlSumStats") @@ -2897,25 +3899,25 @@ setMethod( if (hadJointSpec) { jointResult <- .fmDispatchJointSpecsQtlSumStats( parsedJointSpec, - p$data, + data, intersect(tokens, "mvsusie"), - p$contexts, - p$traitId, - p$coverage, - p$secondaryCoverage, - p$signalCutoff, - p$minAbsCorr, - p$verbose, + contexts, + traitId, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + verbose, methodArgs = methodArgs, - twasWeights = p$twasWeights, - dataDrivenPriorWeightsCutoff = p$dataDrivenPriorWeightsCutoff, - fineMappingResult = p$fineMappingResult, - fullFit = p$fullFit, - fullFitAlphaOnly = p$fullFitAlphaOnly, - includeAllCs = p$includeAllCs, - mafCutoff = p$mafCutoff %||% 0, - macCutoff = p$macCutoff %||% 0, - imissCutoff = p$imissCutoff %||% 1 + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + fineMappingResult = fineMappingResult, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + mafCutoff = mafCutoff %||% 0, + macCutoff = macCutoff %||% 0, + imissCutoff = imissCutoff %||% 1 ) tokens <- setdiff(tokens, c("mvsusie", "fsusie")) methodArgs <- methodArgs[tokens] @@ -2931,16 +3933,20 @@ setMethod( # Resolve the study/context/trait columns and the selected row indices for a # QtlSumStats run, applying the optional contexts / traitId filters. # @noRd -.fmQssSelectRows <- function(p) { - studyCol <- as.character(p$data$study) - contextCol <- as.character(p$data$context) - traitCol <- as.character(p$data$trait) - selRows <- seq_len(nrow(p$data)) - if (!is.null(p$contexts)) { - selRows <- selRows[is_in(contextCol[selRows], p$contexts)] +.fmQssSelectRows <- function( + data, + contexts, + traitId +) { + studyCol <- as.character(data$study) + contextCol <- as.character(data$context) + traitCol <- as.character(data$trait) + selRows <- seq_len(nrow(data)) + if (!is.null(contexts)) { + selRows <- selRows[is_in(contextCol[selRows], contexts)] } - if (!is.null(p$traitId)) { - selRows <- selRows[is_in(traitCol[selRows], p$traitId)] + if (!is.null(traitId)) { + selRows <- selRows[is_in(traitCol[selRows], traitId)] } if (length(selRows) == 0L) { msg <- glue( @@ -2990,38 +3996,60 @@ setMethod( # joint) for mvsusie WITHOUT an explicit jointSpecification, merged with any # explicit-spec result already in `p$jointResult`. # @noRd -.fmQssAutoJoint <- function(p) { - if (length(p$mvTokens) == 0L) { - return(p$jointResult) +.fmQssAutoJoint <- function( + data, + contexts, + traitId, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + fineMappingResult, + twasWeights, + dataDrivenPriorWeightsCutoff, + verbose, + fullFit, + fullFitAlphaOnly, + includeAllCs, + mafCutoff, + macCutoff, + imissCutoff, + methodArgs, + mvTokens, + jointResult, + ldSketch +) { + if (length(mvTokens) == 0L) { + return(jointResult) } autoJoint <- .fmDispatchJointSpecsQtlSumStats( list(list(axes = "context", scope = NULL)), - p$data, - p$mvTokens, - p$contexts, - p$traitId, - p$coverage, - p$secondaryCoverage, - p$signalCutoff, - p$minAbsCorr, - p$verbose, - methodArgs = p$methodArgs, - twasWeights = p$twasWeights, - dataDrivenPriorWeightsCutoff = p$dataDrivenPriorWeightsCutoff, - fineMappingResult = p$fineMappingResult, - fullFit = p$fullFit, - fullFitAlphaOnly = p$fullFitAlphaOnly, - includeAllCs = p$includeAllCs, - mafCutoff = p$mafCutoff %||% 0, - macCutoff = p$macCutoff %||% 0, - imissCutoff = p$imissCutoff %||% 1 - ) - if (is.null(p$jointResult)) { + data, + mvTokens, + contexts, + traitId, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + verbose, + methodArgs = methodArgs, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + fineMappingResult = fineMappingResult, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + mafCutoff = mafCutoff %||% 0, + macCutoff = macCutoff %||% 0, + imissCutoff = imissCutoff %||% 1 + ) + if (is.null(jointResult)) { autoJoint } else if (is.null(autoJoint)) { - p$jointResult + jointResult } else { - .rbindFineMappingResult(p$jointResult, autoJoint, ldSketch = p$ldSketch) + .rbindFineMappingResult(jointResult, autoJoint, ldSketch = ldSketch) } } @@ -3030,7 +4058,13 @@ setMethod( # and combine with the joint result. An all-screened collection yields a valid # empty result rather than an error. # @noRd -.fmQssAssemble <- function(p, rows, nSkipped, jointResult) { +.fmQssAssemble <- function( + data, + ldSketch, + rows, + nSkipped, + jointResult +) { rowContext <- map_chr(rows, "context") rowTrait <- map_chr(rows, "trait") perTupleResult <- if (length(rows) > 0L) { @@ -3040,11 +4074,11 @@ setMethod( rowTrait, map_chr(rows, "method"), map(rows, "entry"), - traitPos = tryCatch( - .anchorVector(p$data, rowContext, rowTrait, "traitPos"), - error = function(e) NULL + traitPos = try_fetch( + .anchorVector(data, rowContext, rowTrait, "traitPos"), + error = function(cnd) NULL ), - ldSketch = p$ldSketch + ldSketch = ldSketch ) } else { NULL @@ -3060,7 +4094,7 @@ setMethod( character(0), character(0), list(), - ldSketch = p$ldSketch, + ldSketch = ldSketch, allowEmpty = TRUE )) } @@ -3071,16 +4105,64 @@ setMethod( if (is.null(perTupleResult)) { return(jointResult) } - .rbindFineMappingResult(perTupleResult, jointResult, ldSketch = p$ldSketch) + .rbindFineMappingResult(perTupleResult, jointResult, ldSketch = ldSketch) } # QtlSumStats fine-mapping worker. `p` is the setMethod's captured arguments; # it is extended via list_modify with the resolved tokens / row selection / LD # sketch so the per-entry dispatch helpers read everything from one bundle. # @noRd -.fmPipelineQtlSumStats <- function(p) { - .fmAssertQcd(p$data) - rt <- .fmQssResolveTokens(p) +.fmPipelineQtlSumStats <- function( + data, + methods, + contexts, + traitId, + jointSpecification, + addSusieInf, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + medianAbsCorr, + fineMappingResult, + twasWeights, + dataDrivenPriorWeightsCutoff, + verbose, + trim, + fullFit, + fullFitAlphaOnly, + includeAllCs, + serFallback, + rFinite, + rMismatch, + rssControl, + keepFullFit, + mafCutoff, + macCutoff, + imissCutoff +) { + .fmAssertQcd(data) + rt <- .fmQssResolveTokens( + data = data, + methods = methods, + contexts = contexts, + traitId = traitId, + jointSpecification = jointSpecification, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + verbose = verbose, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ) if (rt$exhaustedByJoint) { if (is.null(rt$jointResult)) { msg <- glue( @@ -3092,36 +4174,89 @@ setMethod( } return(rt$jointResult) } - sel <- .fmQssSelectRows(p) + sel <- .fmQssSelectRows(data = data, contexts = contexts, traitId = traitId) split <- .fmQssSplitTokens(rt$tokens, sel) - ldSketch <- getLdSketch(p$data) - p <- list_modify( - p, - tokens = rt$tokens, - methodArgs = rt$methodArgs, - jointResult = rt$jointResult, - studyCol = sel$studyCol, - contextCol = sel$contextCol, - traitCol = sel$traitCol, - selRows = sel$selRows, - univTokens = split$univTokens, - mvTokens = split$mvTokens, - ldSketch = ldSketch, - rFiniteResolved = .fmResolveRFinite( - p$rFinite, - p$serFallback, - p$rMismatch, - ldSketch - ) + ldSketch <- getLdSketch(data) + # Derived values are locals, not fields grafted onto a parameter pack. + tokens <- rt$tokens + methodArgs <- rt$methodArgs + jointResult <- rt$jointResult + studyCol <- sel$studyCol + contextCol <- sel$contextCol + traitCol <- sel$traitCol + selRows <- sel$selRows + univTokens <- split$univTokens + mvTokens <- split$mvTokens + rFiniteResolved <- .fmResolveRFinite( + rFinite, + serFallback, + rMismatch, + ldSketch ) - univOut <- if (length(p$univTokens) > 0L) { - map(p$selRows, .fmRssEntryRows, p = p) + univOut <- if (length(univTokens) > 0L) { + map( + selRows, + .fmRssEntryRows, + studyCol = studyCol, + contextCol = contextCol, + traitCol = traitCol, + univTokens = univTokens, + fineMappingResult = fineMappingResult, + data = data, + ldSketch = ldSketch, + addSusieInf = addSusieInf, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + verbose = verbose, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + serFallback = serFallback, + rFiniteResolved = rFiniteResolved, + rMismatch = rMismatch, + rssControl = rssControl, + keepFullFit = keepFullFit, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ) } else { list() } rows <- list_flatten(map(univOut, "rows")) nSkipped <- sum(map_lgl(univOut, "skipped")) - .fmQssAssemble(p, rows, nSkipped, .fmQssAutoJoint(p)) + .fmQssAssemble( + data = data, + ldSketch = ldSketch, + rows = rows, + nSkipped = nSkipped, + jointResult = .fmQssAutoJoint( + data = data, + contexts = contexts, + traitId = traitId, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + verbose = verbose, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff, + methodArgs = methodArgs, + mvTokens = mvTokens, + jointResult = jointResult, + ldSketch = ldSketch + ) + ) } #' @rdname fineMappingPipeline @@ -3159,7 +4294,35 @@ setMethod( imissCutoff = 1, ... ) { - .fmPipelineQtlSumStats(as.list(environment())) + .fmPipelineQtlSumStats( + data = data, + methods = methods, + contexts = contexts, + traitId = traitId, + jointSpecification = jointSpecification, + addSusieInf = addSusieInf, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + medianAbsCorr = medianAbsCorr, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + verbose = verbose, + trim = trim, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + serFallback = serFallback, + rFinite = rFinite, + rMismatch = rMismatch, + rssControl = rssControl, + keepFullFit = keepFullFit, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ) } ) @@ -3185,32 +4348,78 @@ setMethod( } # GwasSumStats fine-mapping worker. `p` is the setMethod's captured arguments -# (as.list(environment())); it is extended via list_modify with the resolved +# resolved tokens / LD sketch / finite-sample size are ordinary locals; the # tokens / LD sketch / finite-sample size so the per-entry dispatch helpers # read everything from the single bundle. One GwasSumStats is one LD # block (the caller builds one collection per block when sweeping the genome); # we fine-map each (study, method) tuple across the whole entry, no in-pipeline # block partitioning. # @noRd -.fmPipelineGwas <- function(p) { - .fmAssertQcd(p$data) - norm <- .fmNormalizeMethods(p$methods, L = p$L, Lgreedy = p$Lgreedy) +.fmPipelineGwas <- function( + data, + methods, + L, + Lgreedy, + addSusieInf, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + fineMappingResult, + verbose, + fullFit, + fullFitAlphaOnly, + includeAllCs, + serFallback, + rFinite, + rMismatch, + rssControl, + keepFullFit, + mafCutoff, + macCutoff, + imissCutoff +) { + .fmAssertQcd(data) + norm <- .fmNormalizeMethods(methods, L = L, Lgreedy = Lgreedy) .fmCheckMethodCapabilities(norm$tokens, "GwasSumStats") - ldSketch <- getLdSketch(p$data) - p <- list_modify( - p, - tokens = norm$tokens, - methodArgs = norm$methodArgs, + ldSketch <- getLdSketch(data) + # Derived values are ordinary locals now, not fields grafted onto a bundle. + tokens <- norm$tokens + methodArgs <- norm$methodArgs + rFiniteResolved <- .fmResolveRFinite( + rFinite, + serFallback, + rMismatch, + ldSketch + ) + studyCol <- as.character(data$study) + entryOut <- map( + seq_len(nrow(data)), + .fmGwasEntryRows, + data = data, + studyCol = studyCol, + tokens = tokens, ldSketch = ldSketch, - rFiniteResolved = .fmResolveRFinite( - p$rFinite, - p$serFallback, - p$rMismatch, - ldSketch - ), - studyCol = as.character(p$data$study) + verbose = verbose, + fineMappingResult = fineMappingResult, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff, + addSusieInf = addSusieInf, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + serFallback = serFallback, + rFiniteResolved = rFiniteResolved, + rMismatch = rMismatch, + rssControl = rssControl, + keepFullFit = keepFullFit ) - entryOut <- map(seq_len(nrow(p$data)), .fmGwasEntryRows, p = p) rows <- list_flatten(map(entryOut, "rows")) nSkipped <- sum(map_lgl(entryOut, "skipped")) # An all-screened (or empty-input) collection legitimately yields a 0-row @@ -3221,7 +4430,7 @@ setMethod( map(rows, "entry"), blockIds = map_chr(rows, "blockId"), ldSketch = ldSketch, - allowEmpty = (nSkipped > 0L || nrow(p$data) == 0L) + allowEmpty = (nSkipped > 0L || nrow(data) == 0L) ) } @@ -3257,7 +4466,30 @@ setMethod( imissCutoff = 1, ... ) { - .fmPipelineGwas(as.list(environment())) + .fmPipelineGwas( + data = data, + methods = methods, + L = L, + Lgreedy = Lgreedy, + addSusieInf = addSusieInf, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + fineMappingResult = fineMappingResult, + verbose = verbose, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + serFallback = serFallback, + rFinite = rFinite, + rMismatch = rMismatch, + rssControl = rssControl, + keepFullFit = keepFullFit, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ) } ) @@ -3298,8 +4530,16 @@ setMethod("fineMappingPipeline", "ANY", function(data, ...) { } # @noRd -.fmGwasLookup <- function(tk, p, st, blockId) { - list(tk = tk, cached = .fmCacheLookupGwasResume(p, st, tk, blockId)) +.fmGwasLookup <- function(tk, fineMappingResult, st, blockId) { + list( + tk = tk, + cached = .fmCacheLookupGwasResume( + fineMappingResult, + st, + tk, + blockId + ) + ) } # @noRd @@ -3313,8 +4553,11 @@ setMethod("fineMappingPipeline", "ANY", function(data, ...) { } # @noRd -.fmQtlLookup <- function(tk, p, st, ctx, tr) { - list(tk = tk, cached = .fmCacheLookup(p$fineMappingResult, st, ctx, tr, tk)) +.fmQtlLookup <- function(tk, fineMappingResult, st, ctx, tr) { + list( + tk = tk, + cached = .fmCacheLookup(fineMappingResult, st, ctx, tr, tk) + ) } # @noRd @@ -3388,20 +4631,71 @@ setMethod("fineMappingPipeline", "ANY", function(data, ...) { } # @noRd -.fmUnivLookup <- function(tk, p, ctx, tid) { +.fmUnivLookup <- function(tk, fineMappingResult, study, ctx, tid) { list( tk = tk, - cached = .fmCacheLookup(p$fineMappingResult, p$study, ctx, tid, tk) + cached = .fmCacheLookup(fineMappingResult, study, ctx, tid, tk) ) } # @noRd -.fmUnivCachedRow <- function(l, p, ctx, tid) { - .fmQtlRow(p$study, ctx, tid, l$tk, l$cached) +.fmUnivCachedRow <- function(l, study, ctx, tid) { + .fmQtlRow(study, ctx, tid, l$tk, l$cached) } # All univariate row-records for one context (over its per-context traits). # @noRd -.fmUnivContextRows <- function(ctx, p) { - list_flatten(map(p$perCtxTraits[[ctx]], .fmUnivTraitRows, p = p, ctx = ctx)) +.fmUnivContextRows <- function( + ctx, + data, + study, + univTokens, + xRegions, + naAction, + fineMappingResult, + cisWindow, + screen, + addSusieInf, + verbose, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + methodArgs, + cvFolds, + cvThreads, + samplePartition, + seed, + fullFit, + fullFitAlphaOnly, + includeAllCs, + perCtxTraits +) { + list_flatten(map( + perCtxTraits[[ctx]], + .fmUnivTraitRows, + ctx = ctx, + data = data, + study = study, + univTokens = univTokens, + xRegions = xRegions, + naAction = naAction, + fineMappingResult = fineMappingResult, + cisWindow = cisWindow, + screen = screen, + addSusieInf = addSusieInf, + verbose = verbose, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + methodArgs = methodArgs, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs + )) } diff --git a/R/fineMappingRow.R b/R/fineMappingRow.R index fb1ce1e7b..a58df40eb 100644 --- a/R/fineMappingRow.R +++ b/R/fineMappingRow.R @@ -41,19 +41,22 @@ setClass( prototype(susieFit = NULL, cvResult = NULL) ) +#' @importFrom checkmate makeAssertCollection assertList methods::setValidity("FineMappingRow", function(object) { - errors <- character(0) - if (!is.null(object@cvResult) && !is.list(object@cvResult)) { - errors <- c(errors, "cvResult must be NULL or a list") - } + coll <- makeAssertCollection() + assertList( + object@cvResult, + null.ok = TRUE, + .var.name = "cvResult", + add = coll + ) + # mcols() is an S4 DataFrame, not a data.frame, so checkDataFrame does not + # apply; the row-count contract stays a plain check. md <- mcols(object@variants, use.names = FALSE) if (!is.null(md) && nrow(md) != length(object@variants)) { - errors <- c( - errors, - "variants' metadata columns must have one row per variant" - ) + coll$push("variants' metadata columns must have one row per variant") } - if (length(errors) == 0L) TRUE else errors + coll$getMessages() }) #' @title Build One Fine-Mapping Row @@ -191,10 +194,11 @@ fineMappingRow <- function(variantIds, susieFit, topLoci, cvResult = NULL) { # call is the only path that works across all post_processing modes. Guarded so # an upstream fsusieR change surfaces as a clear error, not a silent NULL. # @noRd +#' @importFrom rlang try_fetch .fsusiePopulateCredibleBand <- function(fit) { - fn <- tryCatch( + fn <- try_fetch( get("update_cal_credible_band.susiF", envir = asNamespace("fsusieR")), - error = function(e) NULL + error = function(cnd) NULL ) if (is.null(fn)) { # Defensive guard against an upstream fsusieR rename; only reachable @@ -217,7 +221,7 @@ fineMappingRow <- function(variantIds, susieFit, topLoci, cvResult = NULL) { if (is.null(vid) || length(vid) == 0L) { return(NA_character_) } - tryCatch(parseVariantId(vid[1])$chrom, error = function(e) NA_character_) + try_fetch(parseVariantId(vid[1])$chrom, error = function(cnd) NA_character_) } .emptyCredibleBand <- function() { @@ -303,7 +307,7 @@ fineMappingRow <- function(variantIds, susieFit, topLoci, cvResult = NULL) { return(GenomicRanges::GRanges()) } fit <- .fsusiePopulateCredibleBand(fit) - reg <- tryCatch(fsusieR::affected_reg(fit), error = function(e) NULL) + reg <- try_fetch(fsusieR::affected_reg(fit), error = function(cnd) NULL) if (is.null(reg) || nrow(reg) == 0L) { return(GenomicRanges::GRanges()) } @@ -668,21 +672,27 @@ fineMappingRow <- function(variantIds, susieFit, topLoci, cvResult = NULL) { # cs_log10bf: strongest member logBF (NA when the column is absent). # @noRd .csLog10Bf <- function(m) { - if (is_in("logBF", names(m))) { - suppressWarnings(max(m$logBF, na.rm = TRUE)) - } else { - NA_real_ + if (!is_in("logBF", names(m))) { + return(NA_real_) } + # Filtering first avoids max()'s empty-input warning entirely, and keeps + # this branch agreeing with the NA_real_ returned above: an empty or + # all-NA column previously yielded -Inf. + v <- m$logBF[is.finite(m$logBF)] + if (length(v) == 0L) NA_real_ else max(v) } # cs_mean_effect: mean conditional effect over the CS (NA when absent). # @noRd .csMeanEffect <- function(m) { - if (is_in("conditional_effect", names(m))) { - suppressWarnings(mean(as.numeric(m$conditional_effect), na.rm = TRUE)) - } else { - NA_real_ + if (!is_in("conditional_effect", names(m))) { + return(NA_real_) } + # suppressWarnings covers only the coercion; the empty case is handled + # explicitly so it returns NA_real_ rather than NaN. + v <- suppressWarnings(as.numeric(m$conditional_effect)) + v <- v[is.finite(v)] + if (length(v) == 0L) NA_real_ else mean(v) } @@ -1427,7 +1437,7 @@ setMethod("show", "FineMappingRow", function(object) { nCs <- if (nrow(tl) > 0L) { csCols <- names(tl)[str_detect(names(tl), "^cs_[0-9]+$")] if (length(csCols) > 0L) { - length(unique(unlist(map(csCols, .fmeNonNullCsLabels, tl = tl)))) + length(unique(list_c(map(csCols, .fmeNonNullCsLabels, tl = tl)))) } else { 0L } diff --git a/R/fineMappingWrappers.R b/R/fineMappingWrappers.R index 19cd4134c..3bd0eaf32 100644 --- a/R/fineMappingWrappers.R +++ b/R/fineMappingWrappers.R @@ -196,7 +196,6 @@ prepareSusieFromInfArgs <- function( args } -#' @importFrom utils modifyList #' @noRd fitSusieInfThenSusie <- function( X, @@ -218,7 +217,7 @@ fitSusieInfThenSusie <- function( X, y, "susieInf", - userArgs = modifyList(args, susieInfArgs) + userArgs = list_modify(args, !!!compact(susieInfArgs)) ) } else { susieInfFit <- .setFinemappingFitClass(susieInfFit, "susieInf") @@ -230,7 +229,7 @@ fitSusieInfThenSusie <- function( y, "susie", chainFromInf = susieInfFit, - userArgs = modifyList(args, susieArgs) + userArgs = list_modify(args, !!!compact(susieArgs)) ) } else { susieFit <- .setFinemappingFitClass(susieFit, "susie") @@ -269,6 +268,7 @@ fitSusieInfThenSusie <- function( #' n = rep(nrow(X), ncol(X))) #' LD <- cor(X) #' fitSusieInfThenSusieRss(z = stat$z, R = LD, n = nrow(X)) +#' @importFrom checkmate assertNumeric assertNumber assertList #' @export fitSusieInfThenSusieRss <- function( z, @@ -279,6 +279,12 @@ fitSusieInfThenSusieRss <- function( susieArgs = list(), fittedModels = NULL ) { + assertNumeric(z) + assertNumber(n, lower = 0, finite = TRUE) + assertList(args) + assertList(susieInfArgs) + assertList(susieArgs) + assertList(fittedModels, null.ok = TRUE) # RSS analog of fitSusieInfThenSusie, built from the shared per-token RSS # fitter (.fmFitSusieRss). .fmFitSusieRss tags every fit "susieRss", so the # inf fit is re-tagged "susieInf" to preserve this wrapper's contract. @@ -292,7 +298,7 @@ fitSusieInfThenSusieRss <- function( R, n, "susieInf", - userArgs = modifyList(args, susieInfArgs) + userArgs = list_modify(args, !!!compact(susieInfArgs)) ) } susieInfFit <- .setFinemappingFitClass(susieInfFit, "susieInf") @@ -304,7 +310,7 @@ fitSusieInfThenSusieRss <- function( n, "susie", chainFromInf = susieInfFit, - userArgs = modifyList(args, susieArgs) + userArgs = list_modify(args, !!!compact(susieArgs)) ) } susieFit <- .setFinemappingFitClass(susieFit, "susieRss") @@ -386,7 +392,6 @@ postprocessFinemappingFits <- function( fullFitAlphaOnly = TRUE, includeAllCs = FALSE ) { - p <- as.list(environment()) fits <- fits[!map_lgl(fits, is.null)] if (length(fits) == 0) { abort("At least one fine-mapping fit must be supplied.") @@ -394,13 +399,81 @@ postprocessFinemappingFits <- function( if (is.null(names(fits)) || any(names(fits) == "")) { abort("fits must be a named list; names define method identity.") } - .ppFitsCombine(.ppFitsPerMethod(fits, p)) + .ppFitsCombine(.ppFitsPerMethod( + fits, + dataX = dataX, + dataY = dataY, + xScalar = xScalar, + yScalar = yScalar, + af = af, + n = n, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + otherQuantities = otherQuantities, + region = region, + priorEffTol = priorEffTol, + minAbsCorr = minAbsCorr, + medianAbsCorr = medianAbsCorr, + csInput = csInput, + conditionIdx = conditionIdx, + trim = trim, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs + )) } # Post-process each method's fit once (buildTopLoci per fit); the per-method # 22-column contributions are row-bound later into the single top_loci table. -.ppFitsPerMethod <- function(fits, p) { - posts <- map(names(fits), .ppOneFit, fits = fits, p = p) +.ppFitsPerMethod <- function( + fits, + dataX, + dataY, + xScalar, + yScalar, + af, + n, + coverage, + secondaryCoverage, + signalCutoff, + otherQuantities, + region, + priorEffTol, + minAbsCorr, + medianAbsCorr, + csInput, + conditionIdx, + trim, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { + posts <- map( + names(fits), + .ppOneFit, + fits = fits, + dataX = dataX, + dataY = dataY, + xScalar = xScalar, + yScalar = yScalar, + af = af, + n = n, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + otherQuantities = otherQuantities, + region = region, + priorEffTol = priorEffTol, + minAbsCorr = minAbsCorr, + medianAbsCorr = medianAbsCorr, + csInput = csInput, + conditionIdx = conditionIdx, + trim = trim, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs + ) names(posts) <- names(fits) posts } @@ -538,101 +611,180 @@ postprocessFinemappingFit.susiF <- function( csInput = c("X", "Xcorr", "fsusie") ) { csInput <- arg_match(csInput) - p <- as.list(environment()) variantNames <- extractVariantNames(fit) sumstats <- extractSumstats(fit, dataX, dataY, xScalar, yScalar, method) - csTables <- .ppCsTables(p, csInput) + csTables <- .ppCsTables( + csInput, + fit = fit, + dataX = dataX, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + minAbsCorr = minAbsCorr, + medianAbsCorr = medianAbsCorr, + method = method + ) # Always build the canonical unfiltered table; the FineMappingRow stores # it as-is so accessors can filter by PIP at query time. - topLociFull <- .ppTopLoci(p, csTables, variantNames, sumstats) + topLociFull <- .ppTopLoci( + csTables, + variantNames, + sumstats, + fit = fit, + method = method, + af = af, + n = n, + signalCutoff = signalCutoff, + dataY = dataY, + otherQuantities = otherQuantities, + region = region, + conditionIdx = conditionIdx, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs + ) fmEntry <- fineMappingRow( variantIds = variantNames, - susieFit = .ppStoredFit(p, csTables), + susieFit = .ppStoredFit( + csTables, + fit = fit, + trim = trim, + priorEffTol = priorEffTol, + method = method + ), topLoci = topLociFull ) - .ppAssembleRes(p, topLociFull, fmEntry, sumstats) + .ppAssembleRes( + topLociFull, + fmEntry, + sumstats, + fit = fit, + method = method, + dataY = dataY, + otherQuantities = otherQuantities, + signalCutoff = signalCutoff + ) } # The fit as stored: trim = TRUE keeps a minimal subset, FALSE the full # untrimmed susie return (mu / mu2 / lbf_variable / V / ...). # @noRd -.ppStoredFit <- function(p, csTables) { - if (!isTRUE(p$trim)) { - return(p$fit) +.ppStoredFit <- function( + csTables, + fit, + trim, + priorEffTol, + method +) { + if (!isTRUE(trim)) { + return(fit) } trimFinemappingFit( - p$fit, - selectEffects(p$fit, p$priorEffTol), - p$method, + fit, + selectEffects(fit, priorEffTol), + method, csTables ) } # Credible-set tables for the fit at the requested coverages. -.ppCsTables <- function(p, csInput) { +.ppCsTables <- function( + csInput, + fit, + dataX, + coverage, + secondaryCoverage, + minAbsCorr, + medianAbsCorr, + method +) { computeCsTables( - p$fit, - dataX = p$dataX, - coverage = p$coverage, - secondaryCoverage = p$secondaryCoverage, - method = p$method, + fit, + dataX = dataX, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + method = method, csInput = csInput, - minAbsCorr = p$minAbsCorr, - medianAbsCorr = p$medianAbsCorr + minAbsCorr = minAbsCorr, + medianAbsCorr = medianAbsCorr ) } # Canonical unfiltered top-loci table (signalCutoff = 0). -.ppTopLoci <- function(p, csTables, variantNames, sumstats) { +.ppTopLoci <- function( + csTables, + variantNames, + sumstats, + fit, + method, + af, + n, + signalCutoff, + dataY, + otherQuantities, + region, + conditionIdx, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { buildTopLoci( - p$fit, + fit, csTables, variantNames = variantNames, sumstats = sumstats, - af = p$af, - n = p$n, - method = p$method, + af = af, + n = n, + method = method, signalCutoff = 0, - dataY = p$dataY, - otherQuantities = p$otherQuantities, - region = p$region, - conditionIdx = p$conditionIdx, - fullFit = p$fullFit, - fullFitAlphaOnly = p$fullFitAlphaOnly, - includeAllCs = p$includeAllCs + dataY = dataY, + otherQuantities = otherQuantities, + region = region, + conditionIdx = conditionIdx, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs ) } # Assemble the wrapper-facing result: PIP-filtered top_loci (legacy behaviour # for non-S4 callers) + the entry + optional sumstats/sampleNames/context. -.ppAssembleRes <- function(p, topLociFull, fmEntry, sumstats) { +.ppAssembleRes <- function( + topLociFull, + fmEntry, + sumstats, + fit, + method, + dataY, + otherQuantities, + signalCutoff +) { topLociWrapper <- topLociFull if ( - !is.null(p$signalCutoff) && - p$signalCutoff > 0 && + !is.null(signalCutoff) && + signalCutoff > 0 && nrow(topLociWrapper) > 0L ) { keep <- !is.na(topLociWrapper$pip) & - topLociWrapper$pip > p$signalCutoff + topLociWrapper$pip > signalCutoff topLociWrapper <- topLociWrapper[keep, , drop = FALSE] } res <- list( top_loci = topLociWrapper, finemappingEntry = fmEntry, - method = p$method + method = method ) if (!is.null(sumstats)) { res$sumstats <- sumstats } - sampleNames <- .sampleNamesFromDataY(p$dataY) + sampleNames <- .sampleNamesFromDataY(dataY) if (!is.null(sampleNames)) { res$sampleNames <- sampleNames } - if (p$method == "mvsusie" && !is.null(p$fit$outcome_names)) { - res$contextNames <- p$fit$outcome_names + if (method == "mvsusie" && !is.null(fit$outcome_names)) { + res$contextNames <- fit$outcome_names } - if (!is.null(p$otherQuantities)) { - res$otherQuantities <- p$otherQuantities + if (!is.null(otherQuantities)) { + res$otherQuantities <- otherQuantities } res } @@ -645,7 +797,18 @@ extractVariantNames <- function(fit) { if (is.null(variantNames)) { variantNames <- str_c("variant_", seq_along(fit$pip)) } - tryCatch(normalizeVariantId(variantNames), error = function(e) variantNames) + try_fetch( + normalizeVariantId(variantNames), + error = function(cnd) { + msg <- glue( + "variant ids could not be normalised; using them as given. ", + "Downstream joins that assume the canonical form may not ", + "match." + ) + warn(msg, parent = cnd) + variantNames + } + ) } extractSumstats <- function( @@ -808,9 +971,9 @@ computeCsTable <- function( # (fsusieR::cal_purity), recorded as sets$purity$min.abs.corr for the canonical # .csPurityVec() reader; cs_corr keeps the BETWEEN-CS correlation matrix. .csTableFsusie <- function(fit, dataX, coverage) { - sets <- tryCatch( + sets <- try_fetch( fsusieGetCs(fit, dataX, requestedCoverage = coverage), - error = function(e) list(cs = list(), requested_coverage = coverage) + error = function(cnd) list(cs = list(), requested_coverage = coverage) ) if ( is.null(sets$cs) || @@ -821,9 +984,9 @@ computeCsTable <- function( return(list(sets = sets, pip = fit$pip)) } if (requireNamespace("fsusieR", quietly = TRUE)) { - purity <- tryCatch( + purity <- try_fetch( as.numeric(unlist(fsusieR::cal_purity(sets$cs, dataX))), - error = function(e) NULL + error = function(cnd) NULL ) if (!is.null(purity) && length(purity) == length(sets$cs)) { sets$purity <- tibble(min.abs.corr = purity) @@ -1314,55 +1477,90 @@ buildTopLoci <- function( if (missing(method)) { method <- NULL } - p <- as.list(environment()) .btlValidateMethod(method) if (length(variantNames) == 0L) { return(.emptyTopLoci()) } - .btlBuild(p) + .btlBuild( + fit = fit, + csTables = csTables, + variantNames = variantNames, + sumstats = sumstats, + af = af, + n = n, + method = method, + signalCutoff = signalCutoff, + dataY = dataY, + otherQuantities = otherQuantities, + region = region, + conditionIdx = conditionIdx, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs + ) } -# Orchestrate the top-loci table from the captured argument list `p`. -.btlBuild <- function(p) { - nV <- length(p$variantNames) - cov <- .btlCoverage(p$csTables) - fc <- .btlFitConstants(p$dataY, p$otherQuantities, p$region) - post <- .btlPosterior(p$fit, p$conditionIdx, nV) - marg <- .btlMarginal(p$sumstats, nV) - cs <- .btlCsMembership(cov, p$csTables, nV, p$variantNames) +# Orchestrate the top-loci table from the buildTopLoci() arguments. +.btlBuild <- function( + fit, + csTables, + variantNames, + sumstats, + af, + n, + method, + signalCutoff, + dataY, + otherQuantities, + region, + conditionIdx, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { + nV <- length(variantNames) + cov <- .btlCoverage(csTables) + fc <- .btlFitConstants(dataY, otherQuantities, region) + post <- .btlPosterior(fit, conditionIdx, nV) + marg <- .btlMarginal(sumstats, nV) + cs <- .btlCsMembership(cov, csTables, nV, variantNames) fullFitBlock <- .btlFullFitBlock( - p$fit, + fit, post, cov, cs, - p$csTables, + csTables, nV, - p[c("fullFit", "fullFitAlphaOnly", "includeAllCs")] + list( + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs + ) ) out <- .btlAssemble( - p$variantNames, - .btlParseVariants(p$variantNames), + variantNames, + .btlParseVariants(variantNames), fc, marg, post, - p$fit, - p$af, - p$n, - p$method, - .btlCsBlock(p$method, cs, nV), + fit, + af, + n, + method, + .btlCsBlock(method, cs, nV), fullFitBlock, nV ) cond <- .btlConditional( - p$fit, - p$method, - p$conditionIdx, + fit, + method, + conditionIdx, cov, cs$covSorted, - p$csTables, + csTables, nV ) - .btlFinalize(out, cond, p$conditionIdx, p$signalCutoff) + .btlFinalize(out, cond, conditionIdx, signalCutoff) } # Attach per-condition columns (multi-condition fits) and apply the PIP cutoff. @@ -1380,13 +1578,10 @@ buildTopLoci <- function( # buildTopLoci step helpers --------------------------------------------------- # `method` is required and must be a single non-empty, non-NA string. +#' @importFrom checkmate checkString .btlValidateMethod <- function(method) { - if ( - is.null(method) || - length(method) != 1L || - is.na(method) || - str_length(method) == 0L - ) { + res <- checkString(method, min.chars = 1L) + if (!isTRUE(res)) { abort( "buildTopLoci: `method` is required (e.g. \"susie\", \"susieInf\")." ) @@ -1487,13 +1682,12 @@ buildTopLoci <- function( } # Parse variant IDs to chrom/pos/A1/A2; error on missing or invalid coordinates. +#' @importFrom rlang try_fetch .btlParseVariants <- function(variantNames) { - parsed <- tryCatch( + parsed <- try_fetch( suppressWarnings(parseVariantId(variantNames)), - error = function(e) { - eMsg <- conditionMessage(e) - msg <- glue("buildTopLoci: parseVariantId failed: {eMsg}") - abort(msg) + error = function(cnd) { + abort("buildTopLoci: parseVariantId failed", parent = cnd) } ) if (is.null(parsed) || nrow(parsed) != length(variantNames)) { @@ -1603,7 +1797,7 @@ buildTopLoci <- function( identical(method, "mvsusie") && requireNamespace("mvsusieR", quietly = TRUE) ) { - cm <- tryCatch(mvsusieR::coef.mvsusie(fit), error = function(e) NULL) + cm <- try_fetch(mvsusieR::coef.mvsusie(fit), error = function(cnd) NULL) if (!is.null(cm)) as.matrix(cm)[-1L, , drop = FALSE] else NULL } else { NULL @@ -1842,9 +2036,10 @@ buildTopLoci <- function( ) { return(c(start = NA_integer_, end = NA_integer_)) } - pr <- tryCatch(parseRegion(as.character(regionStr)), error = function(e) { - NULL - }) + pr <- try_fetch( + parseRegion(as.character(regionStr)), + error = function(cnd) NULL + ) if (is.null(pr) || !is.data.frame(pr)) { return(c(start = NA_integer_, end = NA_integer_)) } @@ -2022,7 +2217,10 @@ getCsIndex <- function(snpsIdx, susieCs) { } #' @noRd getTopVariantsIdx <- function(susieOutput, signalCutoff) { - c(which(susieOutput$pip >= signalCutoff), unlist(susieOutput$sets$cs)) |> + # `sets$cs` is absent when no credible set was found; list_c() is strict + # about NULL where unlist() silently returned it. + cs <- list_c(susieOutput$sets$cs %||% list()) + c(which(susieOutput$pip >= signalCutoff), cs) |> unique() |> sort() } @@ -2067,7 +2265,10 @@ calPurity <- function(lCs, X, method = "min") { tt <- list() for (k in seq_along(lCs)) { - csIndices <- unlist(lCs[[k]]) + # `lCs[[k]]` is documented (and always supplied) as a plain index + # vector; the unlist() here was a no-op that quietly tolerated a + # nested list, leaving the contract unsettled. + csIndices <- lCs[[k]] if (method == "min") { if (length(csIndices) == 1) { tt[[k]] <- 1 @@ -2101,6 +2302,7 @@ calPurity <- function(lCs, X, method = "min") { } +#' @importFrom checkmate assertNumber #' @title Create Sets Similar to SuSiE Output from fSuSiE Object #' #' @description This function constructs a list that mimics the structure of @@ -2126,6 +2328,7 @@ calPurity <- function(lCs, X, method = "min") { #' fsusieGetCs(fit) #' @export fsusieGetCs <- function(fsusieObj, X, requestedCoverage = 0.95) { + assertNumber(requestedCoverage, lower = 0, upper = 1) # Create 'cs' set with names csNamed <- set_names( fsusieObj$cs, @@ -2179,7 +2382,8 @@ fsusieGetCs <- function(fsusieObj, X, requestedCoverage = 0.95) { #' @param maxScale numeric, define the maximum of wavelet coefficients used in #' the analysis (2^maxScale). Set 10 true by default. #' @param minPurity Minimum purity threshold for credible sets to be retained. -#' @param ... Additional arguments passed to the fsusie function. +#' @param methodArgs Optional named list of options passed to +#' \code{fsusieR::susiF}. #' @return A modified fsusie object with the susie sets list, correlations for #' cs, alpha as df like susie, and without the dummy cs that do not meet the #' minimum purity requirement. @@ -2206,27 +2410,26 @@ fsusieWrapper <- function( covLev, minPurity, maxScale, - ... + methodArgs = list() ) { if (!requireNamespace("fsusieR", quietly = TRUE)) { - msg <- glue( - "To use this function, please install fsusieR: ", - "https://github.com/stephenslab/fsusieR" - ) - abort(msg) + abort("Package 'fsusieR' is required for this function.") } - fsusieObj <- fsusieR::susiF( - X = X, - Y = Y, - pos = pos, - L = L, - prior = prior, - max_SNP_EM = maxSnpEm, - cov_lev = covLev, - min_purity = minPurity, - max_scale = maxScale, - ... + callArgs <- list_modify( + list( + X = X, + Y = Y, + pos = pos, + L = L, + prior = prior, + max_SNP_EM = maxSnpEm, + cov_lev = covLev, + min_purity = minPurity, + max_scale = maxScale + ), + !!!methodArgs ) + fsusieObj <- exec(fsusieR::susiF, !!!callArgs) .fsusieWrapperPostprocess(fsusieObj, X, minPurity, covLev) } @@ -2265,7 +2468,8 @@ fsusieWrapper <- function( #' \code{mvsusieR::create_mixture_prior(R = ncol(Y))} unless you have a #' domain-specific prior. #' @param coverage Credible set coverage (default 0.95). -#' @param ... Additional arguments forwarded to \code{mvsusieR::mvsusie}. +#' @param methodArgs Optional named list of options forwarded to +#' \code{mvsusieR::mvsusie}. #' @return The fit object returned by \code{mvsusieR::mvsusie}. #' @examples #' \donttest{ @@ -2278,15 +2482,26 @@ fsusieWrapper <- function( #' fitMvsusie(X, Y, #' prior_variance = mvsusieR::create_mixture_prior(R = ncol(Y))) #' } +#' @importFrom checkmate assertNumber #' @export -fitMvsusie <- function(X, Y, prior_variance, coverage = 0.95, ...) { - mvsusieR::mvsusie( - X = X, - Y = Y, - prior_variance = prior_variance, - coverage = coverage, - ... +fitMvsusie <- function( + X, + Y, + prior_variance, + coverage = 0.95, + methodArgs = list() +) { + assertNumber(coverage, lower = 0, upper = 1) + callArgs <- list_modify( + list( + X = X, + Y = Y, + prior_variance = prior_variance, + coverage = coverage + ), + !!!methodArgs ) + exec(mvsusieR::mvsusie, !!!callArgs) } #' Fit mvSuSiE-RSS on summary-statistic (Z, R, N) data @@ -2300,7 +2515,8 @@ fitMvsusie <- function(X, Y, prior_variance, coverage = 0.95, ...) { #' @param N Scalar sample size (median across conditions when N varies). #' @param prior_variance Prior variance matrix. #' @param coverage Credible set coverage (default 0.95). -#' @param ... Additional arguments forwarded to \code{mvsusieR::mvsusie_rss}. +#' @param methodArgs Optional named list of options forwarded to +#' \code{mvsusieR::mvsusie_rss}. #' @return The fit object returned by \code{mvsusieR::mvsusie_rss}. #' @examples #' data(eqtlRegionExample) @@ -2316,16 +2532,29 @@ fitMvsusie <- function(X, Y, prior_variance, coverage = 0.95, ...) { #' n = rep(nrow(X), ncol(X))) #' LD <- cor(X) #' fitMvsusieRss(Z = stat$z, R = LD, N = nrow(X), prior_variance = 1) +#' @importFrom checkmate assertNumber #' @export -fitMvsusieRss <- function(Z, R, N, prior_variance, coverage = 0.95, ...) { - mvsusieR::mvsusie_rss( - Z = Z, - R = R, - N = N, - prior_variance = prior_variance, - coverage = coverage, - ... +fitMvsusieRss <- function( + Z, + R, + N, + prior_variance, + coverage = 0.95, + methodArgs = list() +) { + assertNumber(N, lower = 0, finite = TRUE) + assertNumber(coverage, lower = 0, upper = 1) + callArgs <- list_modify( + list( + Z = Z, + R = R, + N = N, + prior_variance = prior_variance, + coverage = coverage + ), + !!!methodArgs ) + exec(mvsusieR::mvsusie_rss, !!!callArgs) } #' Fit fSuSiE on individual-level (X, Y, pos) data @@ -2335,7 +2564,8 @@ fitMvsusieRss <- function(Z, R, N, prior_variance, coverage = 0.95, ...) { #' @param X Numeric matrix of genotypes (samples x variants). #' @param Y Numeric matrix of multi-trait outcomes (samples x traits). #' @param pos Numeric vector of trait positions (length \code{ncol(Y)}). -#' @param ... Additional arguments forwarded to \code{fsusieR::susiF}. +#' @param methodArgs Optional named list of options forwarded to +#' \code{fsusieR::susiF}. #' @return The fit object returned by \code{fsusieR::susiF}. #' @examples #' data(eqtlRegionExample) @@ -2346,10 +2576,11 @@ fitMvsusieRss <- function(Z, R, N, prior_variance, coverage = 0.95, ...) { #' Y <- matrix(rep(base, each = n), n, nPos) + #' X[, 1] %o% (0.5 * cos(seq(0, pi, length.out = nPos))) #' pos <- seq_len(nPos) -#' fitFsusie(X, Y, pos = pos, L = 2) +#' fitFsusie(X, Y, pos = pos, methodArgs = list(L = 2)) #' @export -fitFsusie <- function(X, Y, pos, ...) { - fsusieR::susiF(X = X, Y = Y, pos = pos, ...) +fitFsusie <- function(X, Y, pos, methodArgs = list()) { + callArgs <- list_modify(list(X = X, Y = Y, pos = pos), !!!methodArgs) + exec(fsusieR::susiF, !!!callArgs) } # ============================================================================= @@ -2385,11 +2616,16 @@ fitFsusie <- function(X, Y, pos, ...) { y, requiredFields, token = "susie", - userArgs = list(), retainFit = FALSE ) { if (is.null(fit)) { - fit <- .fmFitSusieIndiv(X, y, token, userArgs = userArgs) + msg <- glue( + "{token}Weights: no '{token}' fit supplied. These extract weights ", + "from an existing fit and never run fine-mapping themselves; run ", + "fineMappingPipeline() with method '{token}' first and pass the ", + "fit in." + ) + abort(msg) } if (!is.null(X) && length(fit$pip) != ncol(X)) { nPip <- length(fit$pip) @@ -2413,36 +2649,37 @@ fitFsusie <- function(X, Y, pos, ...) { #' Compute SuSiE TWAS weights #' -#' Extracts coefficients from an existing SuSiE fit or fits `susieR::susie()` +#' Extracts coefficients from an existing SuSiE fit. #' from `X` and `y` before extracting weights. #' -#' @param X Genotype matrix. Required when `susieFit` is NULL. -#' @param y Phenotype vector. Required when `susieFit` is NULL. +#' @param X Optional genotype matrix; when supplied it is only used to +#' check that the fit covers the same number of variants. +#' @param y Unused; retained for signature compatibility. #' @param susieFit Optional fitted SuSiE object. #' @param retainFit If TRUE, stores the fitted object as an attribute on the #' returned weights. -#' @param ... Additional arguments passed to `susieR::susie()` when fitting. #' @return Numeric vector of variant weights. #' @examples #' data(eqtlRegionExample) #' X <- eqtlRegionExample$X[, 1:30] #' y <- eqtlRegionExample$yRes -#' susieWeights(X, y) +#' fit <- susieR::susie(X, y, L = 5) +#' susieWeights(susieFit = fit) +#' @importFrom checkmate assertFlag #' @export susieWeights <- function( X = NULL, y = NULL, susieFit = NULL, - retainFit = FALSE, - ... + retainFit = FALSE ) { + assertFlag(retainFit) .susieExtractWeights( susieFit, X, y, requiredFields = c("alpha", "mu", "X_column_scale_factors"), token = "susie", - userArgs = list(...), retainFit = retainFit ) } @@ -2452,33 +2689,34 @@ susieWeights <- function( #' Extracts coefficients from an existing SuSiE-ASH fit or fits #' `susieR::susie()` with `unmappable_effects = "ash"`. #' -#' @param X Genotype matrix. Required when `susieAshFit` is NULL. -#' @param y Phenotype vector. Required when `susieAshFit` is NULL. +#' @param X Optional genotype matrix; when supplied it is only used to +#' check that the fit covers the same number of variants. +#' @param y Unused; retained for signature compatibility. #' @param susieAshFit Optional fitted SuSiE-ASH object. #' @param retainFit If TRUE, stores the fitted object as an attribute on the #' returned weights. -#' @param ... Additional arguments passed to `susieR::susie()` when fitting. #' @return Numeric vector of variant weights. #' @examples #' data(eqtlRegionExample) #' X <- eqtlRegionExample$X[, 1:30] #' y <- eqtlRegionExample$yRes -#' susieAshWeights(X, y) +#' fit <- susieR::susie(X, y, L = 5) +#' susieAshWeights(susieAshFit = fit) +#' @importFrom checkmate assertFlag #' @export susieAshWeights <- function( X = NULL, y = NULL, susieAshFit = NULL, - retainFit = FALSE, - ... + retainFit = FALSE ) { + assertFlag(retainFit) .susieExtractWeights( susieAshFit, X, y, requiredFields = c("alpha", "mu", "theta", "X_column_scale_factors"), token = "susieAsh", - userArgs = list(...), retainFit = retainFit ) } @@ -2499,33 +2737,34 @@ susieAshWeights <- function( #' per-variant PIPs as a gate on whether to use the weights should be aware #' that low or zero PIPs do not imply zero TWAS weights here. #' -#' @param X Genotype matrix. Required when `susieInfFit` is NULL. -#' @param y Phenotype vector. Required when `susieInfFit` is NULL. +#' @param X Optional genotype matrix; when supplied it is only used to +#' check that the fit covers the same number of variants. +#' @param y Unused; retained for signature compatibility. #' @param susieInfFit Optional fitted SuSiE-inf object. #' @param retainFit If TRUE, stores the fitted object as an attribute on the #' returned weights. -#' @param ... Additional arguments passed to `susieR::susie()` when fitting. #' @return Numeric vector of variant weights. #' @examples #' data(eqtlRegionExample) #' X <- eqtlRegionExample$X[, 1:30] #' y <- eqtlRegionExample$yRes -#' susieInfWeights(X, y) +#' fit <- susieR::susie(X, y, L = 5) +#' susieInfWeights(susieInfFit = fit) +#' @importFrom checkmate assertFlag #' @export susieInfWeights <- function( X = NULL, y = NULL, susieInfFit = NULL, - retainFit = FALSE, - ... + retainFit = FALSE ) { + assertFlag(retainFit) .susieExtractWeights( susieInfFit, X, y, requiredFields = c("alpha", "mu", "theta", "X_column_scale_factors"), token = "susieInf", - userArgs = list(...), retainFit = retainFit ) } @@ -2540,11 +2779,16 @@ susieInfWeights <- function( n, requiredFields, token = "susie", - userArgs = list(), retainFit = FALSE ) { if (is.null(fit)) { - fit <- .fmFitSusieRss(z, R, n, token, userArgs = userArgs) + msg <- glue( + "{token}RssWeights: no '{token}' fit supplied. These extract ", + "weights from an existing fit and never run fine-mapping ", + "themselves; run fineMappingPipeline() with method '{token}' ", + "first and pass the fit in." + ) + abort(msg) } if (length(fit$pip) != nrow(R)) { nPip <- length(fit$pip) @@ -2574,12 +2818,9 @@ susieInfWeights <- function( #' @param stat List with components \code{z} (z-scores), \code{n} (sample #' sizes). #' @param LD LD correlation matrix. -#' @param susieRssFit Optional pre-fitted SuSiE-RSS object. +#' @param susieRssFit A fitted SuSiE-RSS object. Required: these wrappers +#' extract weights and never run fine-mapping themselves. #' @param retainFit If TRUE, stores the fitted object as an attribute. -#' @param methodArgs Named list of additional arguments passed to -#' \code{susieR::susie_rss()}. Use this instead of \code{...} to avoid partial -#' matching of short argument names (e.g. \code{L}) to the \code{LD} -#' parameter. #' @return Numeric vector of variant weights. #' @examples #' data(eqtlRegionExample) @@ -2594,15 +2835,18 @@ susieInfWeights <- function( #' z = vapply(ss, function(s) s[1] / s[2], numeric(1)), #' n = rep(nrow(X), ncol(X))) #' LD <- cor(X) -#' susieRssWeights(stat, LD) +#' fit <- susieR::susie_rss(z = stat$z, R = LD, n = nrow(X), L = 5) +#' susieRssWeights(stat, LD, susieRssFit = fit) +#' @importFrom checkmate assertList assertFlag #' @export susieRssWeights <- function( stat, LD, susieRssFit = NULL, - retainFit = TRUE, - methodArgs = list() + retainFit = TRUE ) { + assertList(stat) + assertFlag(retainFit) .susieRssExtractWeights( fit = susieRssFit, z = stat$z, @@ -2610,7 +2854,6 @@ susieRssWeights <- function( n = median(stat$n), requiredFields = c("alpha", "mu", "X_column_scale_factors"), token = "susie", - userArgs = methodArgs, retainFit = retainFit ) } @@ -2636,15 +2879,18 @@ susieRssWeights <- function( #' z = vapply(ss, function(s) s[1] / s[2], numeric(1)), #' n = rep(nrow(X), ncol(X))) #' LD <- cor(X) -#' susieInfRssWeights(stat, LD) +#' fit <- susieR::susie_rss(z = stat$z, R = LD, n = nrow(X), L = 5) +#' susieInfRssWeights(stat, LD, susieInfRssFit = fit) +#' @importFrom checkmate assertList assertFlag #' @export susieInfRssWeights <- function( stat, LD, susieInfRssFit = NULL, - retainFit = TRUE, - methodArgs = list() + retainFit = TRUE ) { + assertList(stat) + assertFlag(retainFit) .susieRssExtractWeights( fit = susieInfRssFit, z = stat$z, @@ -2652,7 +2898,6 @@ susieInfRssWeights <- function( n = median(stat$n), requiredFields = c("alpha", "mu", "theta", "X_column_scale_factors"), token = "susieInf", - userArgs = methodArgs, retainFit = retainFit ) } @@ -2678,15 +2923,18 @@ susieInfRssWeights <- function( #' z = vapply(ss, function(s) s[1] / s[2], numeric(1)), #' n = rep(nrow(X), ncol(X))) #' LD <- cor(X) -#' susieAshRssWeights(stat, LD) +#' fit <- susieR::susie_rss(z = stat$z, R = LD, n = nrow(X), L = 5) +#' susieAshRssWeights(stat, LD, susieAshRssFit = fit) +#' @importFrom checkmate assertList assertFlag #' @export susieAshRssWeights <- function( stat, LD, susieAshRssFit = NULL, - retainFit = TRUE, - methodArgs = list() + retainFit = TRUE ) { + assertList(stat) + assertFlag(retainFit) .susieRssExtractWeights( fit = susieAshRssFit, z = stat$z, @@ -2694,27 +2942,16 @@ susieAshRssWeights <- function( n = median(stat$n), requiredFields = c("alpha", "mu", "theta", "X_column_scale_factors"), token = "susieAsh", - userArgs = methodArgs, retainFit = retainFit ) } #' Compute mvSuSiE TWAS weights #' -#' Extracts coefficients from an existing mvSuSiE fit or fits `fitMvsusie()` -#' from `X` and `Y`. +#' Extracts coefficients from an existing mvSuSiE fit. This never fits +#' mvSuSiE itself: fine-mapping belongs to \code{fineMappingPipeline()}, and a +#' missing fit is an error rather than an invitation to refit. #' -#' @param mvsusieFit Optional fitted mvSuSiE object. -#' @param X Genotype matrix. Required when `mvsusieFit` is NULL. -#' @param Y Phenotype matrix. Required when `mvsusieFit` is NULL. -#' @param priorVariance Optional mvSuSiE prior variance list. -#' @param residualVariance Optional residual variance matrix. -#' @param L Maximum number of components. Default \code{10}, matching -#' \code{mvsusieR::mvsusie}. -#' @param LGreedy Integer or \code{NULL}. Number of greedily-added components. -#' \code{NULL} (default) disables the greedy loop and fits \code{L} -#' directly. -#' @param verbose If TRUE, prints mvSuSiE fitting progress. -#' @param ... Additional arguments passed to `fitMvsusie()` when fitting. +#' @param mvsusieFit A fitted mvSuSiE object. Required. #' @return Matrix of variant weights. #' @examples #' \donttest{ @@ -2723,52 +2960,23 @@ susieAshRssWeights <- function( #' data(multiTraitData) #' X <- multiTraitData$X[, 1:60] #' Y <- multiTraitData$Y -#' mvsusieWeights(X = X, Y = Y, L = 5, LGreedy = 2) +#' fit <- fitMvsusie(X = X, Y = Y, methodArgs = list(L = 5)) +#' mvsusieWeights(mvsusieFit = fit) #' } #' @export -mvsusieWeights <- function( - mvsusieFit = NULL, - X = NULL, - Y = NULL, - priorVariance = NULL, - residualVariance = NULL, - L = 10, - LGreedy = NULL, - verbose = FALSE, - ... -) { +mvsusieWeights <- function(mvsusieFit = NULL) { if (!requireNamespace("mvsusieR", quietly = TRUE)) { - msg <- glue( - "Package 'mvsusieR' is required. Install with: ", - "devtools::install_github('stephenslab/mvsusieR')" - ) - abort(msg) + abort("Package 'mvsusieR' is required.") } if (is.null(mvsusieFit)) { - inform("mvsusieFit is not provided; fitting mvSuSiE now ...") - if (is.null(X) || is.null(Y)) { - abort("Both X and Y must be provided if mvsusieFit is NULL.") - } - if (is.null(priorVariance)) { - priorVariance <- mvsusieR::create_mixture_prior(R = ncol(Y)) - } - if (!is.null(LGreedy)) { - LGreedy <- min(LGreedy, L) - } - - mvsusieFit <- fitMvsusie( - X = X, - Y = Y, - L = L, - L_greedy = LGreedy, - prior_variance = priorVariance, - residual_variance = residualVariance, - estimate_residual_variance = TRUE, - verbose = verbose, - ... + msg <- glue( + "mvsusieWeights: `mvsusieFit` is required. This extracts weights ", + "from an existing mvSuSiE fit and never runs fine-mapping ", + "itself; fit it via fineMappingPipeline() and pass the result in." ) + abort(msg) } - return(mvsusieR::coef.mvsusie(mvsusieFit)[-1, ]) + mvsusieR::coef.mvsusie(mvsusieFit)[-1, ] } # One wavelet basis row: inverse-DWT (wr) of the unit coefficient vector e_k, @@ -3015,18 +3223,9 @@ fsusieWeights <- function( #' @param stat A list with \code{z} (matrix variants x conditions) and \code{n} #' (numeric vector or scalar). #' @param LD LD correlation matrix. -#' @param mvsusieRssFit Optional pre-fitted \code{mvsusieRss} object. -#' @param priorVariance Optional mvSuSiE prior variance specification. When -#' NULL, \code{mvsusieR::create_mixture_prior()} is used with \code{R = -#' ncol(stat$z)}. -#' @param residualVariance Optional residual covariance matrix. -#' @param L Maximum number of single effects. Default \code{10}, matching -#' \code{mvsusieR::mvsusie}. -#' @param LGreedy Integer or \code{NULL}. Number of greedily-added effects. -#' \code{NULL} (default) disables the greedy loop and fits \code{L} -#' directly. +#' @param mvsusieRssFit A fitted \code{mvsusieRss} object. Required: this +#' extracts weights and never runs fine-mapping itself. #' @param retainFit If TRUE, attaches the fitted object as an attribute. -#' @param ... Additional arguments forwarded to \code{mvsusieR::mvsusie_rss}. #' #' @return A numeric matrix of per-variant per-context weights (variants x #' conditions). @@ -3051,30 +3250,30 @@ mvsusieRssWeights <- function( stat, LD, mvsusieRssFit = NULL, - priorVariance = NULL, - residualVariance = NULL, - L = 10, - LGreedy = NULL, - retainFit = FALSE, - ... + retainFit = FALSE ) { if (!requireNamespace("mvsusieR", quietly = TRUE)) { + abort("Package 'mvsusieR' is required.") + } + # The multi-context contract is checked here rather than in a fitting + # branch, so a single-context `stat` is still rejected by name. + Z <- if (is.matrix(stat$z)) stat$z else as.matrix(stat$z) + if (ncol(Z) < 2) { msg <- glue( - "Package 'mvsusieR' is required. ", - "Install with: devtools::install_github('stephenslab/mvsusieR')" + "mvsusieRssWeights expects stat$z to have >= 2 columns ", + "(one per context). For single-context use ", + "susieRssWeights()." ) abort(msg) } if (is.null(mvsusieRssFit)) { - mvsusieRssFit <- .mvsusieRssBuildFit( - stat, - LD, - priorVariance, - residualVariance, - L, - LGreedy, - ... + msg <- glue( + "mvsusieRssWeights: `mvsusieRssFit` is required. This extracts ", + "weights from an existing mvSuSiE-RSS fit and never runs ", + "fine-mapping itself; fit it via fineMappingPipeline() and pass ", + "the result in." ) + abort(msg) } weights <- mvsusieR::coef.mvsusie(mvsusieRssFit)[-1, , drop = FALSE] if (retainFit) { @@ -3083,45 +3282,6 @@ mvsusieRssWeights <- function( weights } -# Build the mvsusie-RSS fit from summary stats when the caller supplied none. -# @noRd -.mvsusieRssBuildFit <- function( - stat, - LD, - priorVariance, - residualVariance, - L, - LGreedy, - ... -) { - Z <- if (is.matrix(stat$z)) stat$z else as.matrix(stat$z) - if (ncol(Z) < 2) { - msg <- glue( - "mvsusieRssWeights expects stat$z to have >= 2 columns ", - "(one per context). For single-context use ", - "susieRssWeights()." - ) - abort(msg) - } - # mvsusieR::mvsusie_rss expects N to be a single scalar - nScalar <- as.numeric(stats::median(stat$n)) - if (is.null(priorVariance)) { - priorVariance <- mvsusieR::create_mixture_prior(R = ncol(Z)) - } - if (!is.null(LGreedy)) { - LGreedy <- min(LGreedy, L) - } - fitMvsusieRss( - Z = Z, - R = LD, - N = nScalar, - L = L, - L_greedy = LGreedy, - prior_variance = priorVariance, - residual_variance = residualVariance, - ... - ) -} # ============================================================================= # Cross-condition credible-set merging @@ -3163,7 +3323,7 @@ mvsusieRssWeights <- function( # Merge overlapping credible sets using connected components (union-find). # @noRd .mergeAndUpdateOverlapSets <- function(variantsSetsAndPipsList, overlapSets) { - allSets <- unique(unlist(overlapSets)) + allSets <- unique(list_c(overlapSets)) if (length(allSets) == 0) { return(list()) } @@ -3334,8 +3494,12 @@ mergeSusieCs <- function(fineMappingResult, coverage = 0.95) { #' @examples #' data(qtlSumStatsExample) #' getSusieResult(qtlSumStatsExample) +#' @importFrom checkmate assertList #' @export getSusieResult <- function(conData) { + # No type guard: this is duck-typed on `$` and `length()` and returns NULL + # for anything without a `finemappingEntry`. Its own @example passes a + # QtlSumStats, which assertList rejects. if (length(conData) == 0) { return(NULL) } @@ -3355,8 +3519,10 @@ getSusieResult <- function(conData) { #' This function extracts and processes information for each Credible Set (CS) #' from finemapping results, typically obtained from a finemapping RDS file. #' -#' @param fmRow A \code{\link{fineMappingRow}} carrying the SuSiE -#' fit and variant ids (e.g. from \code{\link{getFineMappingResult}}). +#' @param fmRow A \code{\link{fineMappingRow}}, or a single-row +#' fine-mapping collection as returned by +#' \code{\link{getFineMappingResult}}, carrying the SuSiE fit and +#' variant ids. #' @param csNames Character vector. Names of the Credible Sets, usually in the #' format "L_". #' @param topLociTable Data frame. The top-loci table (e.g. from @@ -3405,8 +3571,10 @@ getSusieResult <- function(conData) { #' extractCsInfo(fe, csNames = "L_1", topLociTable = tl, #' ldSource = qtlSumStatsExample) #' +#' @importFrom checkmate assertClass assertCharacter #' @export extractCsInfo <- function(fmRow, csNames, topLociTable, ldSource) { + assertCharacter(csNames, any.missing = FALSE) fm <- fmRow trimmed <- .fmrPartsSusieFit(fm) variantNames <- .fmrPartsVariantIds(fm) @@ -3428,8 +3596,10 @@ extractCsInfo <- function(fmRow, csNames, topLociTable, ldSource) { #' Posterior Inclusion Probability (PIP) from finemapping results, typically #' used when no Credible Sets (CS) are identified in the analysis. #' -#' @param fmRow A \code{\link{fineMappingRow}} carrying the SuSiE -#' fit and variant ids (e.g. from \code{\link{getFineMappingResult}}). +#' @param fmRow A \code{\link{fineMappingRow}}, or a single-row +#' fine-mapping collection as returned by +#' \code{\link{getFineMappingResult}}, carrying the SuSiE fit and +#' variant ids. #' @param sumstats A list or data frame carrying a \code{z} element aligned to #' the fit's variants (\code{sumstats$z}). #' @@ -3465,6 +3635,7 @@ extractCsInfo <- function(fmRow, csNames, topLociTable, ldSource) { #' fe <- fineMappingRow(variantIds = vids, susieFit = fit, topLoci = tl) #' extractTopPipInfo(fe, sumstats = list(z = c(1.0, 3.5, -0.5))) #' +#' @importFrom checkmate assertClass #' @export extractTopPipInfo <- function(fmRow, sumstats) { fm <- fmRow @@ -3603,7 +3774,7 @@ extractTopPipInfo <- function(fmRow, sumstats) { refineDefault = if (token == "susie") TRUE else NULL, unmappableEffects = if (token == "susieAsh") "ash" else "none" ) - baseArgs <- modifyList(baseArgs, chainedArgs) + baseArgs <- list_modify(baseArgs, !!!compact(chainedArgs)) baseArgs$X <- X baseArgs$y <- y baseArgs$coverage <- coverage @@ -3721,7 +3892,7 @@ extractTopPipInfo <- function(fmRow, sumstats) { refineDefault = if (token == "susie") TRUE else NULL, unmappableEffects = if (token == "susieAsh") "ash" else "none" ) - baseArgs <- modifyList(baseArgs, chainedArgs) + baseArgs <- list_modify(baseArgs, !!!compact(chainedArgs)) baseArgs$z <- z baseArgs$R <- R baseArgs$n <- n @@ -3782,42 +3953,107 @@ extractTopPipInfo <- function(fmRow, sumstats) { includeAllCs = FALSE, seed = NULL ) { - p <- as.list(environment()) chainLocal <- .fmResolveSusieChain(toRun, addSusieInf) - infFit <- .fmXInfFit(p, chainLocal) + infFit <- .fmXInfFit( + chainLocal, + X = X, + y = y, + coverage = coverage, + methodArgs = methodArgs, + verbose = verbose, + ctx = ctx, + tid = tid + ) out <- list() for (tk in toRun) { - fit <- .fmXFitOne(tk, p, chainLocal, infFit) + fit <- .fmXFitOne( + tk, + chainLocal, + infFit, + X = X, + y = y, + coverage = coverage, + methodArgs = methodArgs, + verbose = verbose, + ctx = ctx, + tid = tid + ) if (is.null(fit)) { next } - out[[tk]] <- .fmXPostprocess(fit, tk, p) + out[[tk]] <- .fmXPostprocess( + fit, + tk, + X = X, + y = y, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + af = af, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs + ) } - .fmXCrossValidate(out, p) + .fmXCrossValidate( + out, + X = X, + y = y, + coverage = coverage, + methodArgs = methodArgs, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + seed = seed, + verbose = verbose, + ctx = ctx, + tid = tid + ) } # Fit the shared susieInf model once, if the requested chain needs it. -.fmXInfFit <- function(p, chainLocal) { +.fmXInfFit <- function( + chainLocal, + X, + y, + coverage, + methodArgs, + verbose, + ctx, + tid +) { if (!chainLocal$runInf) { return(NULL) } - if (p$verbose >= 1) { + if (verbose >= 1) { msg <- glue( - "Fitting susieInf for (context='{p$ctx}', trait='{p$tid}') ..." + "Fitting susieInf for (context='{ctx}', trait='{tid}') ..." ) inform(msg) } .fmFitSusieIndiv( - p$X, - p$y, + X, + y, "susieInf", - coverage = p$coverage, - userArgs = p$methodArgs[["susieInf"]] + coverage = coverage, + userArgs = methodArgs[["susieInf"]] ) } # Resolve the fit for one method token; NULL means "skip this token". -.fmXFitOne <- function(tk, p, chainLocal, infFit) { +.fmXFitOne <- function( + tk, + chainLocal, + infFit, + X, + y, + coverage, + methodArgs, + verbose, + ctx, + tid +) { if (tk == "susieInf") { if (!chainLocal$keepInf) { return(NULL) @@ -3832,65 +4068,91 @@ extractTopPipInfo <- function(fmRow, sumstats) { } else { NULL } - if (p$verbose >= 1) { + if (verbose >= 1) { msg <- glue( - "Fitting {tk} for (context='{p$ctx}', trait='{p$tid}') ..." + "Fitting {tk} for (context='{ctx}', trait='{tid}') ..." ) inform(msg) } .fmFitSusieIndiv( - p$X, - p$y, + X, + y, tk, chainFromInf = chainFrom, - coverage = p$coverage, - userArgs = p$methodArgs[[tk]] + coverage = coverage, + userArgs = methodArgs[[tk]] ) } # Post-process one individual-level fit into a finemapping entry. -.fmXPostprocess <- function(fit, tk, p) { +.fmXPostprocess <- function( + fit, + tk, + X, + y, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + af, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { .fmPostprocessOne( fit = fit, method = tk, - dataX = p$X, - dataY = p$y, - coverage = p$coverage, - secondaryCoverage = p$secondaryCoverage, - signalCutoff = p$signalCutoff, - minAbsCorr = p$minAbsCorr, - af = p$af, + dataX = X, + dataY = y, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + af = af, csInput = "X", - fullFit = p$fullFit, - fullFitAlphaOnly = p$fullFitAlphaOnly, - includeAllCs = p$includeAllCs + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs ) } # Per-fold cross-validation across the fitted methods; attach each method's # out-of-fold predictions to its entry. -.fmXCrossValidate <- function(out, p) { - if (!(p$cvFolds > 1L && length(out) > 0L)) { +.fmXCrossValidate <- function( + out, + X, + y, + coverage, + methodArgs, + cvFolds, + cvThreads, + samplePartition, + seed, + verbose, + ctx, + tid +) { + if (!(cvFolds > 1L && length(out) > 0L)) { return(out) } - if (p$verbose >= 1) { + if (verbose >= 1) { msg <- glue( - "Cross-validating ({p$cvFolds} folds) for ", - "(context='{p$ctx}', trait='{p$tid}') ..." + "Cross-validating ({cvFolds} folds) for ", + "(context='{ctx}', trait='{tid}') ..." ) inform(msg) } cv <- .fmWeightsCv( - p$X, - p$y, + X, + y, names(out), - p$methodArgs, - p$cvFolds, - samplePartition = p$samplePartition, - coverage = p$coverage, - verbose = p$verbose, - numThreads = p$cvThreads, - seed = p$seed + methodArgs, + cvFolds, + samplePartition = samplePartition, + coverage = coverage, + verbose = verbose, + numThreads = cvThreads, + seed = seed ) for (tk in names(out)) { out[[tk]] <- .fmAttachCv(out[[tk]], .fmSliceCv(cv, tk)) @@ -3926,18 +4188,57 @@ extractTopPipInfo <- function(fmRow, sumstats) { rssControl = NULL, keepFullFit = "fallback" ) { - p <- as.list(environment()) chainLocal <- .fmResolveSusieChain(toRun, addSusieInf) - infFit <- .fmRssInfFit(p, chainLocal) + infFit <- .fmRssInfFit( + chainLocal, + z = z, + R = R, + n = n, + coverage = coverage, + methodArgs = methodArgs, + rFinite = rFinite, + rMismatch = rMismatch, + rssControl = rssControl, + verbose = verbose, + label = label + ) out <- list() for (tk in toRun) { - f <- .fmRssFitOne(tk, p, chainLocal, infFit) + f <- .fmRssFitOne( + tk, + chainLocal, + infFit, + z = z, + R = R, + n = n, + coverage = coverage, + methodArgs = methodArgs, + rFinite = rFinite, + rMismatch = rMismatch, + rssControl = rssControl, + verbose = verbose, + label = label, + serFallback = serFallback + ) if (is.null(f)) { next } - ent <- .fmRssPostprocess(f$fit, p) - if (f$isStd && isTRUE(p$serFallback)) { - ent <- .fmRssRecordFallback(ent, f, p$keepFullFit) + ent <- .fmRssPostprocess( + f$fit, + R = R, + z = z, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + af = af, + nVar = nVar, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs + ) + if (f$isStd && isTRUE(serFallback)) { + ent <- .fmRssRecordFallback(ent, f, keepFullFit) } out[[tk]] <- ent } @@ -3945,30 +4246,57 @@ extractTopPipInfo <- function(fmRow, sumstats) { } # Fit the shared susieInf (RSS) model once, if the requested chain needs it. -.fmRssInfFit <- function(p, chainLocal) { +.fmRssInfFit <- function( + chainLocal, + z, + R, + n, + coverage, + methodArgs, + rFinite, + rMismatch, + rssControl, + verbose, + label +) { if (!chainLocal$runInf) { return(NULL) } - if (p$verbose >= 1) { - msg <- glue("Fitting susieInf (RSS) for {p$label} ...") + if (verbose >= 1) { + msg <- glue("Fitting susieInf (RSS) for {label} ...") inform(msg) } .fmFitSusieRss( - p$z, - p$R, - p$n, + z, + R, + n, "susieInf", - coverage = p$coverage, - userArgs = p$methodArgs[["susieInf"]], - rFinite = p$rFinite, - rMismatch = p$rMismatch, - rssControl = p$rssControl + coverage = coverage, + userArgs = methodArgs[["susieInf"]], + rFinite = rFinite, + rMismatch = rMismatch, + rssControl = rssControl ) } # Standard multi-effect SuSiE-RSS fit (susie / susieAsh): the only branch that # carries susieR's finite-sample R diagnostics and honours the SER fallback. -.fmRssFitStd <- function(tk, p, chainLocal, infFit) { +.fmRssFitStd <- function( + tk, + chainLocal, + infFit, + z, + R, + n, + coverage, + methodArgs, + rFinite, + rMismatch, + rssControl, + verbose, + label, + serFallback +) { chainFrom <- if ( (tk == "susie" && chainLocal$chainSusie) || (tk == "susieAsh" && chainLocal$chainAsh) @@ -3977,21 +4305,21 @@ extractTopPipInfo <- function(fmRow, sumstats) { } else { NULL } - if (p$verbose >= 1) { - msg <- glue("Fitting {tk} (RSS) for {p$label} ...") + if (verbose >= 1) { + msg <- glue("Fitting {tk} (RSS) for {label} ...") inform(msg) } fit <- .fmFitSusieRss( - p$z, - p$R, - p$n, + z, + R, + n, tk, chainFromInf = chainFrom, - coverage = p$coverage, - userArgs = p$methodArgs[[tk]], - rFinite = p$rFinite, - rMismatch = p$rMismatch, - rssControl = p$rssControl + coverage = coverage, + userArgs = methodArgs[[tk]], + rFinite = rFinite, + rMismatch = rMismatch, + rssControl = rssControl ) rfd <- fit$R_finite_diagnostics flag <- if (!is.null(rfd) && !is.null(rfd$R_reliability_flag)) { @@ -4000,7 +4328,7 @@ extractTopPipInfo <- function(fmRow, sumstats) { NA } multiFit <- NULL - if (isTRUE(p$serFallback) && isTRUE(flag) && !is.null(rfd$ser_model)) { + if (isTRUE(serFallback) && isTRUE(flag) && !is.null(rfd$ser_model)) { multiFit <- fit fit <- .setFinemappingFitClass(rfd$ser_model, "susieRss") } @@ -4008,7 +4336,22 @@ extractTopPipInfo <- function(fmRow, sumstats) { } # Resolve the fit for one method token; NULL means "skip this token". -.fmRssFitOne <- function(tk, p, chainLocal, infFit) { +.fmRssFitOne <- function( + tk, + chainLocal, + infFit, + z, + R, + n, + coverage, + methodArgs, + rFinite, + rMismatch, + rssControl, + verbose, + label, + serFallback +) { if (tk == "susieInf") { if (!chainLocal$keepInf) { return(NULL) @@ -4016,42 +4359,70 @@ extractTopPipInfo <- function(fmRow, sumstats) { return(list(fit = infFit, flag = NA, isStd = FALSE, multiFit = NULL)) } if (tk == "ser") { - if (p$verbose >= 1) { - msg <- glue("Fitting ser (RSS single-effect) for {p$label} ...") + if (verbose >= 1) { + msg <- glue("Fitting ser (RSS single-effect) for {label} ...") inform(msg) } fit <- .fmFitSusieSer( - p$z, - p$n, - coverage = p$coverage, - userArgs = p$methodArgs[["ser"]] + z, + n, + coverage = coverage, + userArgs = methodArgs[["ser"]] ) return(list(fit = fit, flag = NA, isStd = FALSE, multiFit = NULL)) } - std <- .fmRssFitStd(tk, p, chainLocal, infFit) + std <- .fmRssFitStd( + tk, + chainLocal, + infFit, + z = z, + R = R, + n = n, + coverage = coverage, + methodArgs = methodArgs, + rFinite = rFinite, + rMismatch = rMismatch, + rssControl = rssControl, + verbose = verbose, + label = label, + serFallback = serFallback + ) list(fit = std$fit, flag = std$flag, isStd = TRUE, multiFit = std$multiFit) } # Post-process one RSS fit into a finemapping entry. -.fmRssPostprocess <- function(fit, p) { +.fmRssPostprocess <- function( + fit, + R, + z, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + af, + nVar, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { .fmPostprocessOne( fit = fit, method = "susieRss", - dataX = p$R, - dataY = list(z = p$z), - coverage = p$coverage, - secondaryCoverage = p$secondaryCoverage, - signalCutoff = p$signalCutoff, - minAbsCorr = p$minAbsCorr, - af = p$af, + dataX = R, + dataY = list(z = z), + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + af = af, # Per-variant effective N (reporting-only, top_loci$N). NULL on any path # that has no per-variant N -> buildTopLoci leaves N as NA, never 1. - # This is NOT p$n (the scalar median the RSS fit consumes). - n = p$nVar, + # This is NOT `n` (the scalar median the RSS fit consumes). + n = nVar, csInput = "Xcorr", - fullFit = p$fullFit, - fullFitAlphaOnly = p$fullFitAlphaOnly, - includeAllCs = p$includeAllCs + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs ) } @@ -4103,31 +4474,54 @@ extractTopPipInfo <- function(fmRow, sumstats) { # Post-process one method's fit (buildTopLoci per fit). # @noRd -.ppOneFit <- function(method, fits, p) { +.ppOneFit <- function( + method, + fits, + dataX, + dataY, + xScalar, + yScalar, + af, + n, + coverage, + secondaryCoverage, + signalCutoff, + otherQuantities, + region, + priorEffTol, + minAbsCorr, + medianAbsCorr, + csInput, + conditionIdx, + trim, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { fit <- .setFinemappingFitClass(fits[[method]], method) postprocessFinemappingFit( fit, method = method, - dataX = p$dataX, - dataY = p$dataY, - xScalar = p$xScalar, - yScalar = p$yScalar, - af = p$af, - n = p$n, - coverage = p$coverage, - secondaryCoverage = p$secondaryCoverage, - signalCutoff = p$signalCutoff, - otherQuantities = p$otherQuantities, - region = p$region, - priorEffTol = p$priorEffTol, - minAbsCorr = p$minAbsCorr, - medianAbsCorr = p$medianAbsCorr, - csInput = p$csInput, - conditionIdx = p$conditionIdx, - trim = p$trim, - fullFit = p$fullFit, - fullFitAlphaOnly = p$fullFitAlphaOnly, - includeAllCs = p$includeAllCs + dataX = dataX, + dataY = dataY, + xScalar = xScalar, + yScalar = yScalar, + af = af, + n = n, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + otherQuantities = otherQuantities, + region = region, + priorEffTol = priorEffTol, + minAbsCorr = minAbsCorr, + medianAbsCorr = medianAbsCorr, + csInput = csInput, + conditionIdx = conditionIdx, + trim = trim, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs ) } @@ -4249,15 +4643,15 @@ extractTopPipInfo <- function(fmRow, sumstats) { mergedSets[[variantId]] } else { str_flatten( - sort(unique(unlist(extractedResult[[variantId]]$sets))), + sort(unique(extractedResult[[variantId]]$sets)), "," ) } tibble( variant_id = variantId, credibleSetNames = credibleSetNames, - maxPip = max(unlist(extractedResult[[variantId]]$pips)), - medianPip = median(unlist(extractedResult[[variantId]]$pips)) + maxPip = max(extractedResult[[variantId]]$pips), + medianPip = median(extractedResult[[variantId]]$pips) ) } diff --git a/R/genotypeIo.R b/R/genotypeIo.R index f76688b38..cd6d7dd76 100644 --- a/R/genotypeIo.R +++ b/R/genotypeIo.R @@ -33,8 +33,10 @@ NULL setMethod( "readGenotypes", signature(path = "character"), - function(path, format = NULL, ...) { - .genotypeExperiment(.readGenotypeHandle(path, format = format, ...)) + function(path, format = NULL, vcfArgs = list(), ...) { + .genotypeExperiment( + .readGenotypeHandle(path, format = format, vcfArgs = vcfArgs) + ) } ) @@ -43,8 +45,10 @@ setMethod( setMethod( "readGenotypes", signature(path = "missing"), - function(path, format = NULL, ...) { - .genotypeExperiment(GenotypeHandle(...)) + function(path, format = NULL, vcfArgs = list(), ...) { + .genotypeExperiment( + GenotypeHandle(format = format, vcfArgs = vcfArgs, ...) + ) } ) @@ -52,16 +56,16 @@ setMethod( # handle-construction machinery below calls this rather than readGenotypes() # so it does not wrap and immediately unwrap a panel on every hop. # @noRd -.readGenotypeHandle <- function(path, format = NULL, ...) { +.readGenotypeHandle <- function(path, format = NULL, vcfArgs = list()) { if (is.null(format)) { format <- .h2DetectFormat(path) } switch( format, "gds" = .makeGdsHandle(path), - "vcf" = .makeVcfHandle(path, ...), - "plink1" = .makePlink1Handle(path, ...), - "plink2" = .makePlink2Handle(path, ...), + "vcf" = .makeVcfHandle(path, vcfArgs = vcfArgs), + "plink1" = .makePlink1Handle(path), + "plink2" = .makePlink2Handle(path), .abortUnsupportedFormat(format) ) } @@ -129,6 +133,7 @@ setMethod( handle } +#' @importFrom checkmate assertFileExists #' @keywords internal .makeGdsHandle <- function(path) { if (!requireNamespace("SNPRelate", quietly = TRUE)) { @@ -137,10 +142,7 @@ setMethod( if (!requireNamespace("gdsfmt", quietly = TRUE)) { abort("Package 'gdsfmt' is required for reading GDS files.") } - if (!file.exists(path)) { - msg <- glue("GDS file not found: {path}") - abort(msg) - } + assertFileExists(path, access = "r", .var.name = "GDS file") snpInfo <- .gdsSnpInfo(path) @@ -158,15 +160,13 @@ setMethod( ) } +#' @importFrom checkmate assertFileExists #' @keywords internal -.makeVcfHandle <- function(path, ...) { +.makeVcfHandle <- function(path, vcfArgs = list()) { if (!requireNamespace("VariantAnnotation", quietly = TRUE)) { abort("Package 'VariantAnnotation' is required for reading VCF files.") } - if (!file.exists(path)) { - msg <- glue("VCF file not found: {path}") - abort(msg) - } + assertFileExists(path, access = "r", .var.name = "VCF file") hdr <- VariantAnnotation::scanVcfHeader(path) sampleIds <- as.character(VariantAnnotation::samples(hdr)) @@ -177,7 +177,12 @@ setMethod( info = NA, geno = NA ) - vcf <- VariantAnnotation::readVcf(path, param = param, ...) + vcf <- exec( + VariantAnnotation::readVcf, + path, + param = param, + !!!vcfArgs + ) rd <- rowRanges(vcf) # pecotmr convention: A1 = ALT (effect), A2 = REF @@ -201,7 +206,7 @@ setMethod( } #' @keywords internal -.makePlink1Handle <- function(path, ...) { +.makePlink1Handle <- function(path) { if (!requireNamespace("snpStats", quietly = TRUE)) { abort("Package 'snpStats' is required for reading plink1 files.") } @@ -220,14 +225,14 @@ setMethod( # Resolve the plink1 stem and assert its .bed/.bim/.fam all exist. # @noRd +#' @importFrom checkmate assertFileExists .plink1RequireFiles <- function(path) { stem <- .plinkStem(path) - for (f in str_c(stem, c(".bed", ".bim", ".fam"))) { - if (!file.exists(f)) { - msg <- glue("Plink file not found: {f}") - abort(msg) - } - } + assertFileExists( + str_c(stem, c(".bed", ".bim", ".fam")), + access = "r", + .var.name = "Plink file" + ) stem } @@ -265,7 +270,7 @@ setMethod( } #' @keywords internal -.makePlink2Handle <- function(path, ...) { +.makePlink2Handle <- function(path) { if (!requireNamespace("pgenlibr", quietly = TRUE)) { abort("Package 'pgenlibr' is required for reading plink2 files.") } @@ -606,7 +611,7 @@ extractBlockGenotypes <- function(handle, snpIdx, meanImpute = TRUE) { # trips on disjoint seqlevels) and restore the requested snpIdx order. # @noRd .combineShardedSes <- function(ses, groups) { - ord <- order(unlist(groups, use.names = FALSE)) + ord <- order(unname(list_c(groups))) dosages <- map(ses, .seDosage) combinedDos <- exec(rbind, !!!dosages)[ord, , drop = FALSE] rowRangesList <- unname(map(ses, SummarizedExperiment::rowRanges)) @@ -677,6 +682,7 @@ extractBlockGenotypes <- function(handle, snpIdx, meanImpute = TRUE) { } #' @keywords internal +#' @importFrom rlang try_fetch .extractBlockPlink2 <- function(handle, snpIdx) { # pgenlibr::ReadList returns ALT dosage = A1 dosage in pecotmr convention. # The cached @pgenPtr does not survive saveRDS/readRDS (external pointers @@ -697,13 +703,13 @@ extractBlockGenotypes <- function(handle, snpIdx, meanImpute = TRUE) { if (is.null(ptr)) { ptr <- pgenlibr::NewPgen(paths$pgen) } - geno <- tryCatch( + geno <- try_fetch( pgenlibr::ReadList( ptr, variant_subset = variantSubset, meanimpute = FALSE ), - error = function(e) { + error = function(cnd) { reopened <- pgenlibr::NewPgen(paths$pgen) pgenlibr::ReadList( reopened, @@ -926,11 +932,7 @@ readFam <- function(bed) { # open bed/bim/fam: A PLINK 1 .bed is a valid .pgen openBed <- function(bed) { if (!requireNamespace("pgenlibr", quietly = TRUE)) { - msg <- glue( - "To use this function, please install pgenlibr: ", - "https://cran.r-project.org/web/packages/pgenlibr/index.html" - ) - abort(msg) + abort("Package 'pgenlibr' is required for this function.") } rawSCt <- nrow(readFam(bed)) return(pgenlibr::NewPgen(bed, raw_sample_ct = rawSCt)) @@ -949,7 +951,9 @@ openBed <- function(bed) { #' package = "pecotmr"), "protocol_example.LD.chr22") #' readAfreq(stem) #' @export +#' @importFrom checkmate assertString readAfreq <- function(prefix) { + assertString(prefix) afreqZst <- str_c(prefix, ".afreq.zst") afreqPlain <- str_c(prefix, ".afreq") if (file.exists(afreqZst)) { @@ -1107,20 +1111,13 @@ invertMinmaxScaling <- function(X, uMin, uMax) { # ---------- Internal helpers for PLINK2 format ---------- +#' @importFrom checkmate assertFileExists #' Resolve and validate PLINK2 file paths for a given prefix. #' @return Named list with pgen, pvar, psam paths. #' @noRd resolvePlink2Paths <- function(prefix) { pgen <- str_c(prefix, ".pgen") - if (!file.exists(pgen)) { - msg <- glue( - "PLINK2 .pgen file not found at: {pgen}\n", - " Note: .pgen must be uncompressed (plink2 does not ", - "compress .pgen).", - .trim = FALSE - ) - abort(msg) - } + assertFileExists(pgen, access = "r", .var.name = "PLINK2 .pgen file") # Prefer plain .pvar (fast, no extra deps); fall back to .pvar.zst pvar <- if (file.exists(str_c(prefix, ".pvar"))) { str_c(prefix, ".pvar") @@ -1131,15 +1128,7 @@ resolvePlink2Paths <- function(prefix) { abort(msg) } psam <- str_c(prefix, ".psam") - if (!file.exists(psam)) { - msg <- glue( - "PLINK2 .psam file not found at: {psam}\n", - " Note: .psam must be uncompressed (plink2 does not ", - "compress .psam).", - .trim = FALSE - ) - abort(msg) - } + assertFileExists(psam, access = "r", .var.name = "PLINK2 .psam file") list(pgen = pgen, pvar = pvar, psam = psam) } @@ -1153,11 +1142,7 @@ resolvePlink2Paths <- function(prefix) { #' @noRd readPvar <- function(pvarPath) { if (!requireNamespace("pgenlibr", quietly = TRUE)) { - msg <- glue( - "pgenlibr is required. Install from ", - "https://cran.r-project.org/web/packages/pgenlibr/index.html" - ) - abort(msg) + abort("Package 'pgenlibr' is required.") } pvar <- pgenlibr::NewPvar(pvarPath) on.exit(pgenlibr::ClosePvar(pvar), add = TRUE) @@ -1377,9 +1362,9 @@ getRefVariantInfo <- function(source, region = NULL) { #' @importFrom readr read_lines #' @noRd matchVariantsToKeep <- function(variantInfo, keepVariantsPath) { - keepRaw <- tryCatch( + keepRaw <- try_fetch( as.data.frame(vroom(keepVariantsPath, show_col_types = FALSE)), - error = function(e) NULL + error = function(cnd) NULL ) if ( !is.null(keepRaw) && diff --git a/R/gwasSumStats.R b/R/gwasSumStats.R index eb742045d..fbeb11583 100644 --- a/R/gwasSumStats.R +++ b/R/gwasSumStats.R @@ -22,25 +22,24 @@ NULL #' every row. #' @seealso \code{\link{GwasSumStats}} for the constructor and #' \code{\linkS4class{QtlSumStats}} for the QTL counterpart. +#' @importFrom checkmate makeAssertCollection assertNames assertList #' @export setClass( "GwasSumStats", contains = "SumStatsBase", validity = function(object) { # The ldSketch slot's class union enforces its type. - errors <- character() - required <- "study" - missingCols <- setdiff(required, colnames(mcols(object))) - if (length(missingCols) > 0L) { - errors <- c( - errors, - str_c("missing columns: ", str_flatten(missingCols, ", ")) - ) - } - errors <- c(errors, .sumStatsCheckGenome(object)) - if (!is.list(object@qcInfo)) { - errors <- c(errors, "'qcInfo' slot must be a list") - } + coll <- makeAssertCollection() + assertNames( + colnames(mcols(object)) %||% character(0), + must.include = "study", + what = "colnames", + .var.name = "mcols", + add = coll + ) + coll$push(.sumStatsCheckGenome(object)) + assertList(object@qcInfo, .var.name = "qcInfo", add = coll) + errors <- coll$getMessages() if (length(errors) == 0L) { # The elements ARE GRanges by construction now -- the container is # a GRangesList -- so the old per-element type and length checks diff --git a/R/h2EstimationWrappers.R b/R/h2EstimationWrappers.R index fc83e9ade..9a064a6a4 100644 --- a/R/h2EstimationWrappers.R +++ b/R/h2EstimationWrappers.R @@ -393,6 +393,7 @@ standardizeTauStar <- function(tau, tauBlocks, sdAnnot, MRef, h2g) { metafor::rma(yi = means, sei = ses, method = "DL") } +#' @importFrom rlang try_fetch .rmaMeta <- function(means, ses, method = "DL") { k <- length(means) if (k != length(ses)) { @@ -409,9 +410,9 @@ standardizeTauStar <- function(tau, tauBlocks, sdAnnot, MRef, h2g) { fit <- if (identical(method, "DL")) { metafor::rma(yi = means, sei = ses, method = "DL") } else { - tryCatch( + try_fetch( metafor::rma(yi = means, sei = ses, method = method), - error = function(e) .rmaMetaFallbackToDL(e, means, ses, method) + error = function(cnd) .rmaMetaFallbackToDL(cnd, means, ses, method) ) } list( @@ -496,11 +497,11 @@ NULL # Concatenate per-block stats into the genome-wide regression design. M_a is # the annotation SNP counts (univariate: total number of directions). .lderDesign <- function(blockStats, baselineMat) { - x <- unlist(map(blockStats, "x")) + x <- list_c(map(blockStats, "x")) ldAnnotList <- map(blockStats, "ldAnnot") list( x = x, - lam = unlist(map(blockStats, "lam")), + lam = list_c(map(blockStats, "lam")), ldAnnot = exec(rbind, !!!ldAnnotList), blockId = rep( seq_along(blockStats), @@ -614,8 +615,8 @@ NULL rough = rough, twostage = twostage ) - h2Loo <- unlist(map(loo, "h2")) - aLoo <- unlist(map(loo, "a")) + h2Loo <- map_dbl(loo, "h2") + aLoo <- map_dbl(loo, "a") tauList <- map(loo, "tau") tauBlocks <- exec(rbind, !!!tauList) jkSe <- function(v) sqrt((nB - 1) / nB * sum((v - mean(v))^2)) @@ -941,7 +942,7 @@ NULL estHBlocks <- exec(rbind, !!!estHList) tauList <- map(loo, "tau") tauBlocks <- exec(rbind, !!!tauList) - intLoo <- unlist(map(loo, "intercept")) + intLoo <- map_dbl(loo, "intercept") jkSe <- function(v) sqrt(var(v) * (nB - 1)^2 / nB) list( h2Se = jkSe(estHBlocks[, 1]), @@ -1076,8 +1077,8 @@ gldscUnivariate <- function( M <- length(ldRef) A <- .gldscAnnotMatrix(annotations, M) preps <- map(ldMatrixList, .gldscBlockPrep, z = z, A = A) - rawNtau <- sum(unlist(map(preps, "y"))) / - sum(unlist(map(preps, "ldsc"))) + rawNtau <- sum(list_c(map(preps, "y"))) / + sum(list_c(map(preps, "ldsc"))) contrib <- map(preps, .gldscBlockGls, rawNtau = rawNtau) left <- reduce(map(contrib, "L"), `+`) right <- reduce(map(contrib, "R"), `+`) @@ -1199,8 +1200,8 @@ NULL # eigenvalue-score matrix, and M_a. Univariate uses a single all-ones score # column (l_{i,base} = 1) scaled by total M. .hdlDesign <- function(blockData, M, baselineMat) { - lam <- unlist(map(blockData, "lam")) - bstar <- unlist(map(blockData, "bstar")) + lam <- list_c(map(blockData, "lam")) + bstar <- list_c(map(blockData, "bstar")) blockId <- rep( seq_along(blockData), lengths(map(blockData, "lam")) @@ -1321,7 +1322,7 @@ NULL ) h2aList <- map(loo, "h2a") h2aBlocks <- exec(rbind, !!!h2aList) - intLoo <- unlist(map(loo, "int")) + intLoo <- map_dbl(loo, "int") jkSe <- function(x) sqrt(mean((x - mean(x))^2) * (nBlocks - 1)) list( h2aSe = apply(h2aBlocks, 2, jkSe), @@ -1952,15 +1953,21 @@ sldscUnivariate <- function( ldRef, annotations, local, - ... + estimatorArgs = list() ) { - switch( + # `estimatorArgs` rather than `...`: the four estimators take different + # trailing arguments (`lambda` for lder/gldsc/hdl, `nIter` for sldsc), so + # an unknown name should fail here rather than at whichever estimator the + # method token happens to select. + base <- list(z, n, ldRef, annotations, local) + fn <- switch( method, - "lder" = lderUnivariate(z, n, ldRef, annotations, local, ...), - "gldsc" = gldscUnivariate(z, n, ldRef, annotations, local, ...), - "sldsc" = sldscUnivariate(z, n, ldRef, annotations, local, ...), - "hdl" = hdlUnivariate(z, n, ldRef, annotations, local, ...) + "lder" = lderUnivariate, + "gldsc" = gldscUnivariate, + "sldsc" = sldscUnivariate, + "hdl" = hdlUnivariate ) + exec(fn, !!!base, !!!estimatorArgs) } # The estimators read `z` and the annotation rows positionally, by each LD @@ -2069,6 +2076,7 @@ setMethod( annotations = NULL, local = FALSE, study = NULL, + estimatorArgs = list(), ... ) { method <- arg_match(method, c("lder", "gldsc", "sldsc", "hdl")) @@ -2091,7 +2099,7 @@ setMethod( ldRef, annotations, local, - ... + estimatorArgs = estimatorArgs ) .h2EstimateFromResult(result, method, M, study) } @@ -2224,6 +2232,7 @@ setMethod( # Converter: H2Estimate -> sldsc_wrapper list format # ============================================================================= +#' @importFrom checkmate assertClass #' @title Convert H2Estimate to S-LDSC Trait Format #' @description Convert an \code{H2Estimate} object into the list format #' expected by \code{\link{standardizeSldscTrait}} and @@ -2249,9 +2258,7 @@ setMethod( #' h2EstimateToSldscTrait(h2EstimateExample) #' @export h2EstimateToSldscTrait <- function(h2Est) { - if (!is(h2Est, "H2Estimate")) { - abort("h2Est must be an H2Estimate object") - } + assertClass(h2Est, "H2Estimate") enrichDf <- getEnrichment(h2Est) if (is.null(enrichDf)) { diff --git a/R/jointEngine.R b/R/jointEngine.R index 909d3fae0..99b271124 100644 --- a/R/jointEngine.R +++ b/R/jointEngine.R @@ -109,11 +109,12 @@ NULL # cis-window, so the fitted span IS the region). # Each returns a length-1 GRanges, or NULL when the anchor is unavailable (the # accumulator / builder then records a chrUn sentinel). +#' @importFrom rlang try_fetch .traitPosFor <- function(data, context, trait) { if (methods::is(data, "QtlDataset")) { - se <- tryCatch( + se <- try_fetch( getPhenotypes(data, contexts = context), - error = function(e) NULL + error = function(cnd) NULL ) if (is.null(se)) { return(NULL) @@ -273,13 +274,13 @@ setMethod( "fsusie", args$methodArgs[["fsusie"]] ) - fit <- exec(fitFsusie, !!!fitArgs) + fit <- exec(fitFsusie, !!!.splitMethodArgs(fitFsusie, fitArgs)) # Collapse the functional fit to a variants x features weight matrix now # (trimming later drops fitted_wc/csd_X); store on $coef so a trimmed fit # can still yield TWAS weights. - fit$coef <- tryCatch( + fit$coef <- try_fetch( fsusieWeights(fsusieFit = fit, variantIds = colnames(Xc)), - error = function(e) NULL + error = function(cnd) NULL ) fit <- .setFinemappingFitClass(fit, "fsusie") cvM <- .jointFsusieCv(Xc, Yc, group, cfg, args, verbose) @@ -436,7 +437,7 @@ setMethod( "mvsusie", args$methodArgs[["mvsusie"]] ) - fit <- exec(fitMvsusie, !!!fitArgs) + fit <- exec(fitMvsusie, !!!.splitMethodArgs(fitMvsusie, fitArgs)) fit <- .setFinemappingFitClass(fit, "mvsusie") cvM <- .jointMvCv( Xc, @@ -592,7 +593,10 @@ setMethod( "mvsusie", args$methodArgs[["mvsusie"]] ) - fit <- exec(fitMvsusieRss, !!!fitArgs) + fit <- exec( + fitMvsusieRss, + !!!.splitMethodArgs(fitMvsusieRss, fitArgs) + ) .setFinemappingFitClass(fit, "mvsusie") } @@ -715,7 +719,7 @@ setMethod( nCond <- ncol(Yc) cond <- .jgConditions(group) methodKey <- .twasMethodKey(token) - stdz <- cfg$standardized + stdz <- cfg$standardized %||% FALSE fittedModels <- args$fittedModels %||% list() ma <- .jointTwasMethodArgs( args, @@ -891,6 +895,24 @@ setMethod( # Cross-validated prediction result for a TWAS token: reuse fine-mapping's own # CV when available, else run twasWeightsCv (skipping all-zero-weight methods). +# Whether `token` should be cross-validated. `cvWeightMethods` is the caller's +# explicit override of the CV method set; NULL (the default) means "every +# method that produced non-zero weights", which is what the per-method +# all-zero check below enforces. +# @noRd +.jointTwasCvRequested <- function(cvWeightMethods, token) { + if (is.null(cvWeightMethods)) { + return(TRUE) + } + requested <- if (is.list(cvWeightMethods)) { + names(cvWeightMethods) + } else { + as.character(cvWeightMethods) + } + # Accept either the short token or the `_weights` method key. + is_in(token, str_remove(requested, "(_weights|Weights)$")) +} + # @noRd .jointTwasCv <- function(Xc, Yc, wm, ma, W, args, cfg, token) { cvFolds <- if (is.null(cfg$cvFolds)) 0L else cfg$cvFolds @@ -898,9 +920,23 @@ setMethod( return(NULL) } cvRes <- .twasFmHandoffCv(args$fineMappingCv, token) - if (!is.null(cvRes) || (!is.null(W) && all(W == 0))) { + if (!is.null(cvRes)) { return(cvRes) } + if (!.jointTwasCvRequested(cfg$cvWeightMethods, token)) { + return(NULL) + } + if (!is.null(W) && all(W == 0)) { + # Restored with the notice it used to carry: a method whose weights + # are all zero contributes nothing to cross-validation, and dropping + # it silently made an empty ensemble look like a modelling result. + msg <- glue( + "twasWeightsPipeline: method '{token}' is excluded from ", + "cross-validation because all of its weights are zero." + ) + warn(msg) + return(NULL) + } .jointTwasLeakageWarn(args, ma) verbose <- if (is.null(cfg$verbose)) 1 else cfg$verbose sp <- if (!is.null(args$samplePartition)) { @@ -922,7 +958,7 @@ setMethod( retainFits = TRUE, maxNumVariants = mcv, numThreads = if (is.null(cfg$cvThreads)) 1 else cfg$cvThreads, - data_driven_priorMatricesCv = args$dataDrivenPriorMatricesCv, + dataDrivenPriorMatricesCv = args$dataDrivenPriorMatricesCv, verbose = verbose, seed = cfg$seed ) @@ -1215,8 +1251,8 @@ setMethod("construct", "TwasJointPipeline", function(pipeline, records, ...) { context = as.character(data$context), trait = as.character(data$trait) ) - allCtxs <- unique(unlist(scope$contexts, use.names = FALSE)) - allTrs <- unique(unlist(scope$traits, use.names = FALSE)) + allCtxs <- unique(unname(list_c(scope$contexts))) + allTrs <- unique(unname(list_c(scope$traits))) groups <- list() for (cx in allCtxs) { for (tid in allTrs) { @@ -1610,7 +1646,7 @@ setMethod("construct", "TwasJointPipeline", function(pipeline, records, ...) { cfg$ensembleSolver } alpha <- if (is.null(cfg$ensembleAlpha)) 1 else cfg$ensembleAlpha - stdz <- cfg$standardized + stdz <- cfg$standardized %||% FALSE map( seq_len(nrow(.jgConditions(group))), .twasEnsembleCondition, @@ -1682,7 +1718,7 @@ setMethod("construct", "TwasJointPipeline", function(pipeline, records, ...) { if (length(passing) < 2L) { return(NULL) } - ens <- tryCatch( + ens <- try_fetch( ensembleWeights( cvResults = list( prediction = coll$preds[str_c(passing, "_predicted")] @@ -1693,7 +1729,7 @@ setMethod("construct", "TwasJointPipeline", function(pipeline, records, ...) { solver = solver, alpha = alpha ), - error = function(err) NULL + error = function(cnd) NULL ) if (is.null(ens) || is.null(ens$ensembleTwasWeights)) { return(NULL) diff --git a/R/jointSpecification.R b/R/jointSpecification.R index 72d7b6a46..538f2f6ec 100644 --- a/R/jointSpecification.R +++ b/R/jointSpecification.R @@ -135,10 +135,9 @@ return(character(0)) } if (is.null(context)) { - return(unique(unlist( - map(getContexts(data), .spTraitsInContext, data = data), - use.names = FALSE - ))) + return(unique(unname(list_c( + map(getContexts(data), .spTraitsInContext, data = data) + )))) } # Checked here rather than left to the accessor: `.spListTraits` answers # "which traits are in this scope", and an absent context is an empty @@ -692,6 +691,7 @@ parseTraitIds <- function(traitId, data) { # --- parseMethods helpers --------------------------------------------------- # Validate mutual exclusivity of primary vs split method specs. +#' @importFrom checkmate assertCharacter .parseMethodsValidateArgs <- function( primaryGiven, splitGiven, @@ -720,14 +720,8 @@ parseTraitIds <- function(traitId, data) { ) abort(msg) } - if (!is.character(sumStatsMethods) || length(sumStatsMethods) == 0L) { - abort("`sumStatsMethods` must be a non-empty character vector.") - } - if ( - !is.character(qtlDatasetMethods) || length(qtlDatasetMethods) == 0L - ) { - abort("`qtlDatasetMethods` must be a non-empty character vector.") - } + assertCharacter(sumStatsMethods, min.len = 1L) + assertCharacter(qtlDatasetMethods, min.len = 1L) } } @@ -1659,9 +1653,34 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { # Run the joint dispatch once per region block, then merge per # (study, context, trait, method) across regions. A single block (cis or # jointRegions=TRUE concatenated) returns its result directly. - args <- as.list(environment()) - args$xRegions <- NULL - perRegion <- map(xRegions, .fmDispatchJointSpecRegion, args = args) + # xRegions is deliberately absent: each region is supplied per call. + perRegion <- map( + xRegions, + .fmDispatchJointSpecRegion, + parsedJointSpec = parsedJointSpec, + data = data, + methods = methods, + contexts = contexts, + traitIds = traitIds, + cisWindow = cisWindow, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + verbose = verbose, + methodArgs = methodArgs, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + pipCutoffToSkip = pipCutoffToSkip, + fineMappingResult = fineMappingResult, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + seed = seed + ) perRegion <- compact(perRegion) if (length(perRegion) == 0L) { return(NULL) @@ -1673,26 +1692,38 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { } # FmJointPipeline for individual-level fine-mapping, built from the call params. -.fmJointPipeline <- function(args) { +.fmJointPipeline <- function( + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + dataDrivenPriorWeightsCutoff, + cvFolds, + cvThreads, + samplePartition, + verbose, + fullFit, + fullFitAlphaOnly, + includeAllCs, + seed +) { new( "FmJointPipeline", - config = c( - args[c( - "coverage", - "secondaryCoverage", - "signalCutoff", - "minAbsCorr", - "dataDrivenPriorWeightsCutoff", - "cvFolds", - "cvThreads", - "samplePartition", - "verbose", - "fullFit", - "fullFitAlphaOnly", - "includeAllCs", - "seed" - )], - list(ldSketch = NULL) + config = list( + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + verbose = verbose, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + seed = seed, + ldSketch = NULL ) ) } @@ -1726,7 +1757,21 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { # Engine routing (jointEngine.R); one region block (the caller loops # regions). .jointRejectStudyOnIndividual(parsedJointSpec) - pipeline <- .fmJointPipeline(as.list(environment())) + pipeline <- .fmJointPipeline( + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + verbose = verbose, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + seed = seed + ) .runJointSpecs( parsedJointSpec, data, @@ -1753,22 +1798,32 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { # @noRd # FmJointPipeline for summary-statistics fine-mapping (RSS: no sample folds; # LD sketch drawn from the data). -.fmSumStatsPipeline <- function(args) { +.fmSumStatsPipeline <- function( + data, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + dataDrivenPriorWeightsCutoff, + verbose, + fullFit, + fullFitAlphaOnly, + includeAllCs +) { new( "FmJointPipeline", - config = c( - args[c( - "coverage", - "secondaryCoverage", - "signalCutoff", - "minAbsCorr", - "dataDrivenPriorWeightsCutoff", - "verbose", - "fullFit", - "fullFitAlphaOnly", - "includeAllCs" - )], - list(cvFolds = 0L, ldSketch = getLdSketch(args$data)) + config = list( + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + verbose = verbose, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + cvFolds = 0L, + ldSketch = getLdSketch(data) ) ) } @@ -1798,7 +1853,18 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { # Engine routing (jointEngine.R): the dispatch table + .runJointCell replace # the per-axis switch + the cross-context/trait/study/composed leaf # dispatchers. - pipeline <- .fmSumStatsPipeline(as.list(environment())) + pipeline <- .fmSumStatsPipeline( + data = data, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + verbose = verbose, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs + ) .runJointSpecs( parsedJointSpec, data, @@ -1813,11 +1879,11 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { methodArgs = methodArgs, verbose = verbose, cache = fineMappingResult, - cutoffs = .panelCutoffs(list( + cutoffs = .panelCutoffs( mafCutoff = mafCutoff, macCutoff = macCutoff, imissCutoff = imissCutoff - )) + ) ) ) } @@ -1855,32 +1921,45 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { } # Fine-map the non-study-axis specs on each individual-level QtlDataset. -.fmMultiStudyQtlLoop <- function(nonStudyAxisSpecs, qtlDatasets, args) { +.fmMultiStudyQtlLoop <- function( + nonStudyAxisSpecs, + qtlDatasets, + methods, + contexts, + traitIds, + cisWindow, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + verbose, + methodArgs, + xRegions, + twasWeights, + dataDrivenPriorWeightsCutoff +) { out <- NULL if (length(nonStudyAxisSpecs) == 0L) { return(out) } - fwd <- args[c( - "methods", - "contexts", - "traitIds", - "cisWindow", - "coverage", - "secondaryCoverage", - "signalCutoff", - "minAbsCorr", - "verbose", - "methodArgs", - "xRegions", - "twasWeights", - "dataDrivenPriorWeightsCutoff" - )] for (qdName in names(qtlDatasets)) { - qdArgs <- c( - list(nonStudyAxisSpecs, qtlDatasets[[qdName]]), - fwd + qdRes <- .fmDispatchJointSpecsQtlDataset( + nonStudyAxisSpecs, + qtlDatasets[[qdName]], + methods = methods, + contexts = contexts, + traitIds = traitIds, + cisWindow = cisWindow, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + verbose = verbose, + methodArgs = methodArgs, + xRegions = xRegions, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff ) - qdRes <- exec(.fmDispatchJointSpecsQtlDataset, !!!qdArgs) if (!is.null(qdRes)) { out <- if (is.null(out)) { qdRes @@ -1898,8 +1977,17 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { sumStats, studyAxisSpecs, out, - args, - verbose + methods, + contexts, + traitIds, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + verbose, + methodArgs, + twasWeights, + dataDrivenPriorWeightsCutoff ) { if (is.null(sumStats)) { if (length(studyAxisSpecs) > 0L && verbose >= 1) { @@ -1911,24 +1999,21 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { } return(out) } - fwd <- args[c( - "methods", - "contexts", - "traitIds", - "coverage", - "secondaryCoverage", - "signalCutoff", - "minAbsCorr", - "verbose", - "methodArgs", - "twasWeights", - "dataDrivenPriorWeightsCutoff" - )] - ssArgs <- c( - list(parsedJointSpec, sumStats), - fwd + ssRes <- .fmDispatchJointSpecsQtlSumStats( + parsedJointSpec, + sumStats, + methods = methods, + contexts = contexts, + traitIds = traitIds, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + verbose = verbose, + methodArgs = methodArgs, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff ) - ssRes <- exec(.fmDispatchJointSpecsQtlSumStats, !!!ssArgs) if (is.null(ssRes)) { return(out) } @@ -1957,19 +2042,43 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { twasWeights = NULL, dataDrivenPriorWeightsCutoff = 1e-10 ) { - args <- as.list(environment()) qtlDatasets <- getQtlDatasets(data) sumStats <- getSumStats(data) specs <- .fmSplitStudyAxisSpecs(parsedJointSpec) .fmMultiStudyWarnExcluded(specs$study, qtlDatasets, verbose) - out <- .fmMultiStudyQtlLoop(specs$nonStudy, qtlDatasets, args) + out <- .fmMultiStudyQtlLoop( + specs$nonStudy, + qtlDatasets, + methods = methods, + contexts = contexts, + traitIds = traitIds, + cisWindow = cisWindow, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + verbose = verbose, + methodArgs = methodArgs, + xRegions = xRegions, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff + ) .fmMultiStudySumStats( parsedJointSpec, sumStats, specs$study, out, - args, - verbose + methods = methods, + contexts = contexts, + traitIds = traitIds, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + verbose = verbose, + methodArgs = methodArgs, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff ) } @@ -2145,11 +2254,11 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { args = list( methodArgs = list(), verbose = verbose, - cutoffs = .panelCutoffs(list( + cutoffs = .panelCutoffs( mafCutoff = mafCutoff, macCutoff = macCutoff, imissCutoff = imissCutoff - )) + ) ) ) } @@ -2175,29 +2284,39 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { } # Learn weights for the non-study-axis specs on each individual-level dataset. -.twasMultiStudyQtlLoop <- function(nonStudyAxisSpecs, qtlDatasets, args) { +.twasMultiStudyQtlLoop <- function( + nonStudyAxisSpecs, + qtlDatasets, + methods, + contexts, + traitIds, + cisWindow, + dataType, + verbose, + xRegions, + retainFit, + retainFitDetail, + seed +) { out <- NULL if (length(nonStudyAxisSpecs) == 0L) { return(out) } - fwd <- args[c( - "methods", - "contexts", - "traitIds", - "cisWindow", - "dataType", - "verbose", - "xRegions", - "retainFit", - "retainFitDetail", - "seed" - )] for (qdName in names(qtlDatasets)) { - qdArgs <- c( - list(nonStudyAxisSpecs, qtlDatasets[[qdName]]), - fwd + qdRes <- .twasDispatchJointSpecsQtlDataset( + nonStudyAxisSpecs, + qtlDatasets[[qdName]], + methods = methods, + contexts = contexts, + traitIds = traitIds, + cisWindow = cisWindow, + dataType = dataType, + verbose = verbose, + xRegions = xRegions, + retainFit = retainFit, + retainFitDetail = retainFitDetail, + seed = seed ) - qdRes <- exec(.twasDispatchJointSpecsQtlDataset, !!!qdArgs) if (!is.null(qdRes)) { out <- if (is.null(out)) { qdRes @@ -2215,8 +2334,13 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { sumStats, studyAxisSpecs, out, - args, - verbose + methods, + contexts, + traitIds, + dataType, + verbose, + retainFit, + retainFitDetail ) { if (is.null(sumStats)) { if (length(studyAxisSpecs) > 0L && verbose >= 1) { @@ -2228,20 +2352,17 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { } return(out) } - fwd <- args[c( - "methods", - "contexts", - "traitIds", - "dataType", - "verbose", - "retainFit", - "retainFitDetail" - )] - ssArgs <- c( - list(parsedJointSpec, sumStats), - fwd + ssRes <- .twasDispatchJointSpecsQtlSumStats( + parsedJointSpec, + sumStats, + methods = methods, + contexts = contexts, + traitIds = traitIds, + dataType = dataType, + verbose = verbose, + retainFit = retainFit, + retainFitDetail = retainFitDetail ) - ssRes <- exec(.twasDispatchJointSpecsQtlSumStats, !!!ssArgs) if (is.null(ssRes)) { return(out) } @@ -2267,19 +2388,36 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { retainFitDetail = "slim", seed = NULL ) { - args <- as.list(environment()) qtlDatasets <- getQtlDatasets(data) sumStats <- getSumStats(data) specs <- .fmSplitStudyAxisSpecs(parsedJointSpec) .twasMultiStudyWarnExcluded(specs$study, qtlDatasets, verbose) - out <- .twasMultiStudyQtlLoop(specs$nonStudy, qtlDatasets, args) + out <- .twasMultiStudyQtlLoop( + specs$nonStudy, + qtlDatasets, + methods = methods, + contexts = contexts, + traitIds = traitIds, + cisWindow = cisWindow, + dataType = dataType, + verbose = verbose, + xRegions = xRegions, + retainFit = retainFit, + retainFitDetail = retainFitDetail, + seed = seed + ) .twasMultiStudySumStats( parsedJointSpec, sumStats, specs$study, out, - args, - verbose + methods = methods, + contexts = contexts, + traitIds = traitIds, + dataType = dataType, + verbose = verbose, + retainFit = retainFit, + retainFitDetail = retainFitDetail ) } @@ -2359,9 +2497,58 @@ validateMethodsVsJointSpec <- function(methodsParsed, jointSpecParsed) { # One region's FM joint-spec dispatch; `args` bundles the shared call arguments. # @noRd -.fmDispatchJointSpecRegion <- function(rg, args) { - regionArgs <- c(args, list(region = rg)) - exec(.fmDispatchJointSpecsQtlDatasetOneRegion, !!!regionArgs) +.fmDispatchJointSpecRegion <- function( + rg, + parsedJointSpec, + data, + methods, + contexts, + traitIds, + cisWindow, + coverage, + secondaryCoverage, + signalCutoff, + minAbsCorr, + verbose, + methodArgs, + twasWeights, + dataDrivenPriorWeightsCutoff, + cvFolds, + cvThreads, + samplePartition, + pipCutoffToSkip, + fineMappingResult, + fullFit, + fullFitAlphaOnly, + includeAllCs, + seed +) { + .fmDispatchJointSpecsQtlDatasetOneRegion( + parsedJointSpec = parsedJointSpec, + data = data, + methods = methods, + contexts = contexts, + traitIds = traitIds, + cisWindow = cisWindow, + coverage = coverage, + secondaryCoverage = secondaryCoverage, + signalCutoff = signalCutoff, + minAbsCorr = minAbsCorr, + verbose = verbose, + methodArgs = methodArgs, + twasWeights = twasWeights, + dataDrivenPriorWeightsCutoff = dataDrivenPriorWeightsCutoff, + cvFolds = cvFolds, + cvThreads = cvThreads, + samplePartition = samplePartition, + pipCutoffToSkip = pipCutoffToSkip, + fineMappingResult = fineMappingResult, + fullFit = fullFit, + fullFitAlphaOnly = fullFitAlphaOnly, + includeAllCs = includeAllCs, + seed = seed, + region = rg + ) } # One region's TWAS joint-spec dispatch over a QtlDataset. diff --git a/R/ld.R b/R/ld.R index 6f294c6a7..79a57c217 100644 --- a/R/ld.R +++ b/R/ld.R @@ -842,10 +842,12 @@ isGenotypeSource <- function(path) { #' @importFrom vroom vroom #' @noRd # Read + validate the first row of an LD metadata TSV (>=4 columns). +#' @importFrom checkmate checkFileExists .resolveLdReadMeta <- function(path) { - if (!file.exists(path)) { + res <- checkFileExists(path, access = "r") + if (!isTRUE(res)) { msg <- glue( - "LD metadata file not found: {path}", + "LD metadata file: {res}", "\n Expected: a TSV file with columns chrom, start, end, path.", .trim = FALSE ) @@ -1432,8 +1434,9 @@ loadLdFromGenotype <- function( # not carry them. A malformed sidecar must not take the whole filter down, so # it degrades to "no frequencies here" and the caller falls back to dosage. # @noRd +#' @importFrom rlang try_fetch .panelAfreqTable <- function(prefix) { - af <- tryCatch(readAfreq(prefix), error = function(e) NULL) + af <- try_fetch(readAfreq(prefix), error = function(cnd) NULL) if (is.null(af) || !all(is_in(c("id", "alt_freq"), colnames(af)))) { return(NULL) } @@ -1572,10 +1575,10 @@ loadLdFromGenotype <- function( # The panel-filter cutoffs a pipeline call carries, or NULL when none is set # (so the filter short-circuits without touching the panel). # @noRd -.panelCutoffs <- function(p) { - maf <- p$mafCutoff %||% 0 - mac <- p$macCutoff %||% 0 - imiss <- p$imissCutoff %||% 1 +.panelCutoffs <- function(mafCutoff = 0, macCutoff = 0, imissCutoff = 1) { + maf <- mafCutoff %||% 0 + mac <- macCutoff %||% 0 + imiss <- imissCutoff %||% 1 if (maf <= 0 && mac <= 0 && imiss >= 1) { return(NULL) } @@ -2017,11 +2020,14 @@ loadLdFromBlocks <- function( #' variantIds = c("chr22:16050000:A:G", "chr22:17000000:C:T"), #' ldReferenceMetaFile = meta) #' @export +#' @importFrom checkmate assertCharacter assertFlag filterVariantsByLdReference <- function( variantIds, ldReferenceMetaFile, keepIndel = TRUE ) { + assertCharacter(variantIds, any.missing = FALSE) + assertFlag(keepIndel) variantsDf <- parseVariantId(variantIds) # Derive region to scope the reference lookup @@ -2131,15 +2137,14 @@ filterVariantsByLdReference <- function( blockMetadata } +#' @importFrom checkmate assertClass partitionLdMatrix <- function( ldData, mergeSmallBlocks = TRUE, minMergedBlockSize = 500, maxMergedBlockSize = 10000 ) { - if (!is(ldData, "LdData")) { - abort("ldData must be an LdData object") - } + assertClass(ldData, "LdData") combinedMatrix <- getCorrelation(ldData) blockMetadata <- getBlockMetadata(ldData) if (is(blockMetadata, "GRanges")) { @@ -2604,7 +2609,7 @@ ldPruneByCorrelation <- function( ld.threshold = corThres, verbose = verbose ) - keepIds <- sort(unlist(keepList, use.names = FALSE)) + keepIds <- sort(unname(list_c(keepList))) X.new <- X[, keepIds, drop = FALSE] if (verbose) { nKept <- length(keepIds) @@ -2985,32 +2990,23 @@ enforceDesignFullRank <- function( # Require the bigsnpr/bigstatsr packages used for score-based LD clumping. .ldClumpCheckDeps <- function() { if (!requireNamespace("bigsnpr", quietly = TRUE)) { - msg <- glue( - "Package 'bigsnpr' is required. Install from CRAN: ", - "install.packages('bigsnpr')" - ) - abort(msg) + abort("Package 'bigsnpr' is required.") } if (!requireNamespace("bigstatsr", quietly = TRUE)) { - msg <- glue( - "Package 'bigstatsr' is required. Install from CRAN: ", - "install.packages('bigstatsr')" - ) - abort(msg) + abort("Package 'bigstatsr' is required.") } } # Validate the clumping inputs (dimensions of score/chr/pos vs X). +#' @importFrom checkmate assertVector .ldClumpValidate <- function(X, score, chr, pos) { + # NOT assertMatrix: X may be a bigstatsr FBM, which is not a base matrix. if (ncol(X) < 1L) { abort("ldClumpByScore: X must have at least one column") } - if (!is.null(score) && length(score) != ncol(X)) { - abort("ldClumpByScore: length(score) must equal ncol(X)") - } - if (length(chr) != ncol(X) || length(pos) != ncol(X)) { - abort("ldClumpByScore: chr and pos must have length equal to ncol(X)") - } + assertVector(score, len = ncol(X), null.ok = TRUE) + assertVector(chr, len = ncol(X)) + assertVector(pos, len = ncol(X)) } # Wrap X as a bigstatsr FBM (pass through if already one). @@ -3113,6 +3109,7 @@ ldClumpByScore <- function( # without materializing them all in memory at once. # ============================================================================= +#' @importFrom checkmate assertClass #' Extract the LD or genotype matrix from an LdData S4 object. #' @param ld An LdData object. #' @param wantGenotype Logical; if TRUE, extract the genotype matrix (via @@ -3120,9 +3117,7 @@ ldClumpByScore <- function( #' @return A matrix. #' @noRd extractLdMatrix <- function(ld, wantGenotype = FALSE) { - if (!is(ld, "LdData")) { - abort("ld must be an LdData object") - } + assertClass(ld, "LdData") if (wantGenotype && hasGenotypes(ld)) { return(getGenotypes(ld)) } diff --git a/R/manifestLoaders.R b/R/manifestLoaders.R index 5f9330f77..c13f211e2 100644 --- a/R/manifestLoaders.R +++ b/R/manifestLoaders.R @@ -30,17 +30,14 @@ NULL # Read a manifest into a tibble. A data.frame/tibble is passed through; a # single path is read by extension (.csv -> read_csv, else read_tsv). +#' @importFrom checkmate assertString +#' @importFrom checkmate assertFileExists .readManifest <- function(manifest) { if (is.data.frame(manifest)) { return(as_tibble(manifest, .name_repair = "minimal")) } - if (!is.character(manifest) || length(manifest) != 1L) { - abort("`manifest` must be a data.frame or a single file path.") - } - if (!file.exists(manifest)) { - msg <- glue("manifest file not found: {manifest}") - abort(msg) - } + assertString(manifest, .var.name = "manifest (data.frame or file path)") + assertFileExists(manifest, access = "r", .var.name = "manifest file") if (str_detect(manifest, regex("\\.csv$", ignore_case = TRUE))) { readr::read_csv(manifest, show_col_types = FALSE, progress = FALSE) } else { @@ -77,6 +74,34 @@ NULL # `required` canonical columns are present. `aliases` is a named list keyed by # canonical name; each value is the character vector of accepted source names # (including the canonical name itself). The first alias present wins. +# The QtlDataset pass-through arguments, asserted at the loader so a bad value +# is reported against the argument the caller passed rather than surfacing from +# QtlDataset's validity several hundred lines later. Types match +# .qtlValidateScalars exactly, so this tightens nothing. +# @noRd +#' @importFrom checkmate assertLogical assertNumber assertFlag assertCharacter +.assertQtlPassThrough <- function( + scaleResiduals, + mafCutoff, + macCutoff, + xvarCutoff, + imissCutoff, + keepSamples, + keepVariants, + keepIndel +) { + assertLogical(scaleResiduals, len = 1L) + assertNumber(mafCutoff, lower = 0, finite = TRUE) + assertNumber(macCutoff, lower = 0, finite = TRUE) + assertNumber(xvarCutoff, lower = 0, finite = TRUE) + assertNumber(imissCutoff, lower = 0, finite = TRUE) + assertCharacter(keepSamples) + assertCharacter(keepVariants) + assertFlag(keepIndel) + invisible(NULL) +} + +#' @importFrom checkmate assertNames .canonManifestCols <- function(df, aliases, required, label) { for (canon in names(aliases)) { if (is_in(canon, names(df))) { @@ -87,14 +112,12 @@ NULL names(df)[match(hit[[1L]], names(df))] <- canon } } - missingCols <- setdiff(required, names(df)) - if (length(missingCols) > 0L) { - msg <- glue( - "{label} manifest is missing required column(s): ", - "{str_flatten(missingCols, ', ')}" - ) - abort(msg) - } + assertNames( + names(df), + must.include = required, + what = "colnames", + .var.name = str_c(label, " manifest") + ) df } @@ -295,10 +318,9 @@ NULL # empty entries contribute nothing; NA seqnames are dropped. Always returns a # character vector (character(0) when nothing is present, never NULL). .entriesChroms <- function(entries) { - ch <- as.character(unlist( - map(entries, .mlEntryChroms), - use.names = FALSE - )) + # as.character() is load-bearing: an all-empty list concatenates to NULL, + # and this helper promises character(0). + ch <- as.character(unname(list_c(map(entries, .mlEntryChroms)))) unique(ch[!is.na(ch)]) } @@ -487,7 +509,7 @@ NULL if (is.null(gr)) { return(emptyDf) } - lines <- unlist(Rsamtools::scanTabix(tf, param = gr), use.names = FALSE) + lines <- unname(list_c(Rsamtools::scanTabix(tf, param = gr))) if (length(lines) == 0L) { return(emptyDf) } @@ -562,6 +584,7 @@ NULL # standard key (chrom/pos/variant_id/...) to the source column name. Accepts a # named list/vector, or a path to a YAML file of `standardName: sourceName` # entries (the xqtl-protocol column-mapping format). +#' @importFrom checkmate assertFileExists .readColumnMapping <- function(columnMapping) { if (is.null(columnMapping)) { return(NULL) @@ -574,10 +597,11 @@ NULL return(map_chr(columnMapping, as.character)) } if (is.character(columnMapping) && length(columnMapping) == 1L) { - if (!file.exists(columnMapping)) { - msg <- glue("columnMapping file not found: {columnMapping}") - abort(msg) - } + assertFileExists( + columnMapping, + access = "r", + .var.name = "columnMapping file" + ) mapping <- yaml::read_yaml(columnMapping) if ( !is.list(mapping) || @@ -845,9 +869,11 @@ NULL # effect allele (A1) is ALT; the other allele (A2) is REF. Stats come from the # per-study FORMAT fields ES/SE/LP/SS/EAF, with Z = ES / SE. .vcfToSumstatDf <- function(vcf, sampleSelect, formatMapping, label) { - fmap <- modifyList( + fmap <- list_modify( .gwasVcfFormatDefaults, - if (is.null(formatMapping)) list() else as.list(formatMapping) + !!!compact( + if (is.null(formatMapping)) list() else as.list(formatMapping) + ) ) rr <- SummarizedExperiment::rowRanges(vcf) altList <- VariantAnnotation::alt(vcf) @@ -898,8 +924,8 @@ NULL .readSumStatsVcf <- function(path, region, sampleSelect, formatMapping, label) { if (!requireNamespace("VariantAnnotation", quietly = TRUE)) { msg <- glue( - "{label}: reading VCF sumstats requires the 'VariantAnnotation' ", - "package; please install it." + "{label}: reading VCF sumstats requires the ", + "'VariantAnnotation' package." ) abort(msg) } @@ -1443,6 +1469,7 @@ NULL #' study = "s1", genotypePath = file.path(d, "example.chr22")) #' loadQtlDatasetFromManifest(manifest = manifest, study = "s1") #' @importFrom stringr str_ends +#' @importFrom checkmate assertString assertFlag #' @export loadQtlDatasetFromManifest <- function( manifest, @@ -1459,6 +1486,18 @@ loadQtlDatasetFromManifest <- function( keepIndel = TRUE, transposeCovariates = FALSE ) { + assertString(study, null.ok = TRUE) + assertFlag(transposeCovariates) + .assertQtlPassThrough( + scaleResiduals = scaleResiduals, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + xvarCutoff = xvarCutoff, + imissCutoff = imissCutoff, + keepSamples = keepSamples, + keepVariants = keepVariants, + keepIndel = keepIndel + ) base <- .manifestBase(manifest) df <- .canonManifestCols( .readManifest(manifest), @@ -1955,6 +1994,7 @@ loadQtlSumStatsFromManifest <- function( #' phenotypePath = file.path(d, "example_geneexpr.bed.gz"), #' genotypePath = file.path(d, "example.chr22")) #' loadMultiStudyQtlDatasetFromManifest(qtlDatasetsManifest = manifest) +#' @importFrom checkmate assertFlag assertNumber #' @export loadMultiStudyQtlDatasetFromManifest <- function( qtlDatasetsManifest, @@ -1976,6 +2016,18 @@ loadMultiStudyQtlDatasetFromManifest <- function( keepVariants = character(0), keepIndel = TRUE ) { + assertFlag(transposeCovariates) + assertNumber(minLdOverlapWarn, lower = 0, upper = 1) + .assertQtlPassThrough( + scaleResiduals = scaleResiduals, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + xvarCutoff = xvarCutoff, + imissCutoff = imissCutoff, + keepSamples = keepSamples, + keepVariants = keepVariants, + keepIndel = keepIndel + ) qc <- .msqQcArgs( scaleResiduals, mafCutoff, diff --git a/R/mashPipeline.R b/R/mashPipeline.R index 2bcff321a..0191be0b5 100644 --- a/R/mashPipeline.R +++ b/R/mashPipeline.R @@ -285,11 +285,7 @@ mashResidualCorrelation <- function( method <- arg_match(method) inputScale <- arg_match(inputScale) if (!requireNamespace("mashr", quietly = TRUE)) { - msg <- glue( - "To use this function, please install mashr: ", - "https://cran.r-project.org/web/packages/mashr/index.html" - ) - abort(msg) + abort("Package 'mashr' is required for this function.") } if (methods::is(sumStatsList, "SimpleList")) { sumStatsList <- as.list(sumStatsList) @@ -646,18 +642,10 @@ mashPriorCovariances <- function( # @noRd .mashRequirePriorPackages <- function() { if (!requireNamespace("mashr", quietly = TRUE)) { - msg <- glue( - "To use this function, please install mashr: ", - "https://cran.r-project.org/web/packages/mashr/index.html" - ) - abort(msg) + abort("Package 'mashr' is required for this function.") } if (!requireNamespace("flashier", quietly = TRUE)) { - msg <- glue( - "To use this function, please install flashier: ", - "https://github.com/willwerscheid/flashier" - ) - abort(msg) + abort("Package 'flashier' is required for this function.") } } @@ -792,13 +780,15 @@ mashPriorCovariances <- function( # ud_fit with a directed error for the ud_ted / non-i.i.d. (per-variant SE) # incompatibility. # @noRd +#' @importFrom rlang try_fetch .mashUdFit <- function(fit0, mashData, engine, udControl) { control <- .mashUdControl(engine, udControl, ncol(mashData$Bhat)) - tryCatch( + try_fetch( udr::ud_fit(fit0, control = control, verbose = FALSE), - error = function(e) { + error = function(cnd) { if ( - engine == "ud_ted" && str_detect(conditionMessage(e), "i.i.d") + engine == "ud_ted" && + str_detect(conditionMessage(cnd), "i.i.d") ) { msg <- glue( "mashPriorCovariances: engine 'ud_ted' (udr TED update) ", @@ -806,12 +796,12 @@ mashPriorCovariances <- function( "scale does not provide (per-variant SE). Use engine 'ud' ", "(ED update), or a z-scale input." ) - abort(msg) + abort(msg, parent = cnd) } # Not the ud_ted i.i.d. case rewrapped above -- re-raise the # original condition unchanged so unrelated udr failures surface # (and aren't swallowed as a NULL fit). - cnd_signal(e) + cnd_signal(cnd) } ) } @@ -828,14 +818,15 @@ mashPriorCovariances <- function( ) abort(msg) } - udControl <- utils::modifyList( + # NULL in a user control means "use the default", so drop before merging. + udControl <- list_modify( list( n_unconstrained = 50L, maxiter = 1000L, tol = 1e-2, tol.lik = 1e-2 ), - udControl + !!!compact(udControl) ) U.can <- mashr::cov_canonical(mashData) fit0 <- udr::ud_init( @@ -908,11 +899,7 @@ mashModelFit <- function( fitOn <- arg_match(fitOn) inputScale <- arg_match(inputScale) if (!requireNamespace("mashr", quietly = TRUE)) { - msg <- glue( - "To use this function, please install mashr: ", - "https://cran.r-project.org/web/packages/mashr/index.html" - ) - abort(msg) + abort("Package 'mashr' is required for this function.") } if (methods::is(sumStatsList, "SimpleList")) { sumStatsList <- as.list(sumStatsList) @@ -1009,11 +996,7 @@ mashPosterior <- function( ) { inputScale <- arg_match(inputScale) if (!requireNamespace("mashr", quietly = TRUE)) { - msg <- glue( - "To use this function, please install mashr: ", - "https://cran.r-project.org/web/packages/mashr/index.html" - ) - abort(msg) + abort("Package 'mashr' is required for this function.") } mats <- .mashSumStatsToMatrices(sumStats, "target", inputScale = inputScale) ex <- .mashExcludeConditions( @@ -1088,8 +1071,10 @@ mashPosterior <- function( #' \code{pair[2]}. #' @examples #' makePairwiseContrastCol(c("a", "b"), "mean_contrast_") +#' @importFrom checkmate assertCharacter #' @export makePairwiseContrastCol <- function(pair, template) { + assertCharacter(pair, len = 2L, any.missing = FALSE) template[pair[1]] <- 1 template[pair[2]] <- -1 template @@ -1124,6 +1109,7 @@ makePairwiseContrastCol <- function(pair, template) { #' pv <- array(diag(3) * 0.1, dim = c(3, 3, 1)) #' dimnames(pv) <- list(c("a", "b", "c"), c("a", "b", "c"), NULL) #' fitMashContrast(1L, om, pm, pv) +#' @importFrom checkmate assertCount assertNumeric #' @export fitMashContrast <- function( index, @@ -1132,6 +1118,8 @@ fitMashContrast <- function( posteriorVcov, grouping = NULL ) { + assertCount(index, positive = TRUE) + assertNumeric(grouping, null.ok = TRUE) populationNames <- colnames(posteriorMean) if (!is.null(populationNames)) { populationNames <- str_remove_all(populationNames, "BETA_") @@ -1287,6 +1275,7 @@ fitMashContrast <- function( #' pv <- array(diag(3) * 0.1, dim = c(3, 3, 1)) #' dimnames(pv) <- list(c("a", "b", "c"), c("a", "b", "c"), NULL) #' mashPosteriorContrast(pm, pv, om) +#' @importFrom checkmate assertNumeric #' @export mashPosteriorContrast <- function( posteriorMean, @@ -1294,6 +1283,7 @@ mashPosteriorContrast <- function( origMean, grouping = NULL ) { + assertNumeric(grouping, null.ok = TRUE) origMean <- origMean[, colnames(posteriorMean), drop = FALSE] origMean[is.nan(origMean)] <- 0 @@ -1358,8 +1348,11 @@ mashPosteriorContrast <- function( #' model <- mashModelFit(ssl, alpha = 0, priorCovariances = prior, #' vhat = vhat) #' updateMashModelCov(model, allSamples = conds, samples = conds[1:3]) +#' @importFrom checkmate assertCharacter #' @export updateMashModelCov <- function(mashModel, allSamples, samples) { + assertCharacter(allSamples, any.missing = FALSE) + assertCharacter(samples, any.missing = FALSE) cov <- mashModel$fitted_g$Ulist # Remove matrices for dropped conditions @@ -1430,8 +1423,14 @@ updateMashModelCov <- function(mashModel, allSamples, samples) { #' vhat <- diag(3) #' dimnames(vhat) <- list(cond, cond) #' sliceMashData(dat, vhat = vhat, snps = 1:4, samples = NULL) +#' @importFrom checkmate assertList assertCharacter #' @export sliceMashData <- function(data, vhat, snps, samples) { + assertList(data) + # `snps` and `samples` are SUBSCRIPTS -- `data$bhat[snps, samples]` -- so + # character names, integer indices and NULL are all valid. No type + # assertion is correct here (the @example passes snps = 1:4 and + # samples = NULL). data$bhat <- as.matrix(data$bhat[snps, samples]) data$sbhat <- as.matrix(data$sbhat[snps, samples]) data$Z <- as.matrix(data$Z[snps, samples]) @@ -1452,8 +1451,10 @@ sliceMashData <- function(data, vhat, snps, samples) { #' @return The data list with sanitized values. #' @examples #' sanitizeMashData(list(strong = list(z = matrix(rnorm(9), 3, 3)))) +#' @importFrom checkmate assertList #' @export sanitizeMashData <- function(data) { + assertList(data) data$bhat[is.nan(data$bhat)] <- 0 data$sbhat[is.nan(data$sbhat) | is.infinite(data$sbhat)] <- 1e3 data @@ -1618,8 +1619,10 @@ metaAnalysisPerCondition <- function( #' dimnames(pv) <- list(c("a", "b", "c"), c("a", "b", "c"), NULL) #' cr <- fitMashContrast(1L, om, pm, pv) #' calculateFeatureScores(cr, metaMethod = "mean") +#' @importFrom checkmate assertString #' @export calculateFeatureScores <- function(contrastResult, metaMethod = "REML") { + assertString(metaMethod) cr <- as_tibble(contrastResult) effCols <- names(cr)[str_detect(names(cr), "mean_contrast_.*deviation")] if (length(effCols) == 0L) { @@ -1661,8 +1664,10 @@ calculateFeatureScores <- function(contrastResult, metaMethod = "REML") { #' dimnames(pv) <- list(c("a", "b", "c"), c("a", "b", "c"), NULL) #' cr <- fitMashContrast(1L, om, pm, pv) #' nSignificantScore(cr, pCutoff = 0.05) +#' @importFrom checkmate assertNumber #' @export nSignificantScore <- function(contrastResult, pCutoff = 1e-5) { + assertNumber(pCutoff, lower = 0, upper = 1) cr <- as_tibble(contrastResult) pCols <- names(cr)[str_detect(names(cr), "p_contrast_.*deviation")] if (length(pCols) == 0L) { diff --git a/R/mashWrapper.R b/R/mashWrapper.R index 9fe94e81d..32131b685 100644 --- a/R/mashWrapper.R +++ b/R/mashWrapper.R @@ -54,6 +54,7 @@ filterBySignificance <- function(zMatrix, sigPCutoff) { #' datList <- list(strong = list(z = matrix(rnorm(9), 3, 3))) #' filterInvalidSummaryStat(datList) #' @export +#' @importFrom checkmate assertFlag assertList assertNumber filterInvalidSummaryStat <- function( datList, bhat = NULL, @@ -63,6 +64,16 @@ filterInvalidSummaryStat <- function( sigPCutoff = 1E-6, filterByMissingRate = 0.2 ) { + assertList(datList) + assertFlag(btoz) + # NULL is how callers disable each filter. + assertNumber(sigPCutoff, lower = 0, upper = 1, null.ok = TRUE) + assertNumber( + filterByMissingRate, + lower = 0, + upper = 1, + null.ok = TRUE + ) if ( !is.null(bhat) && !is.null(sbhat) && @@ -177,12 +188,15 @@ filterInvalidSummaryStat <- function( #' }) #' filterMixtureComponents(conditionsToKeep = conditionsToKeep, U = U) #' @export +#' @importFrom checkmate assertCharacter assertNumber filterMixtureComponents <- function( conditionsToKeep, U, w = NULL, wCutoff = 1e-04 ) { + assertCharacter(conditionsToKeep, any.missing = FALSE) + assertNumber(wCutoff, lower = 0, finite = TRUE) conditionsToFilter <- setdiff(colnames(U[[1]]), conditionsToKeep) sumW <- sum(w) U <- .mashSubsetU(U, conditionsToKeep) @@ -345,8 +359,11 @@ mashRandNullSample <- function( #' c("chr1:400:A:G", "chr1:500:A:G", "chr1:600:A:G"), #' c("t1", "t2", "t3"))))) #' mergeMashData(a, b) +#' @importFrom checkmate assertList #' @export mergeMashData <- function(resData, oneData) { + assertList(resData, null.ok = TRUE) + assertList(oneData, null.ok = TRUE) if (length(resData) == 0 || is.null(resData)) { return(oneData) } @@ -944,6 +961,7 @@ qtlSumStatsFromBetaMatrix <- function( # where they don't parse), builds one GRanges entry per condition with mcols # from `mcolFn(j)`, and wraps the entries as a QtlSumStats. # @noRd +#' @importFrom rlang try_fetch .qtlSumStatsFromMatrix <- function( vids, nCond, @@ -959,9 +977,9 @@ qtlSumStatsFromBetaMatrix <- function( context <- .qszmRecycle(context, nCond, "context") trait <- .qszmRecycle(trait, nCond, "trait") # Decode chrom/pos from the variant ids; synthesise where they do not parse. - parsed <- tryCatch( + parsed <- try_fetch( suppressWarnings(parseVariantId(vids)), - error = function(e) NULL + error = function(cnd) NULL ) chrom <- if (!is.null(parsed)) { as.character(parsed$chrom) diff --git a/R/pvalCombine.R b/R/pvalCombine.R index 14fc0df09..d04649f71 100644 --- a/R/pvalCombine.R +++ b/R/pvalCombine.R @@ -38,8 +38,12 @@ NULL #' @return Numeric vector of two-sided p-values. #' @examples #' waldTestPval(beta = 0.3, se = 0.1, n = 1000) +#' @importFrom checkmate assertNumeric #' @export waldTestPval <- function(beta, se, n) { + assertNumeric(beta) + assertNumeric(se) + assertNumeric(n) # Calculate the t statistic tValue <- beta / se # Degrees of freedom @@ -89,11 +93,7 @@ pvalAcat <- function(pvals, naRm = TRUE) { pvalHmp <- function(pvals) { # Make sure harmonicmeanp is installed if (!requireNamespace("harmonicmeanp", quietly = TRUE)) { - msg <- glue( - "To use this function, please install harmonicmeanp: ", - "https://cran.r-project.org/web/packages/harmonicmeanp/index.html" - ) - abort(msg) + abort("Package 'harmonicmeanp' is required for this function.") } # https://search.r-project.org/CRAN/refmans/harmonicmeanp/html/pLandau.html L <- length(pvals) @@ -120,11 +120,7 @@ pvalHmp <- function(pvals) { pvalPoolr <- function(pvals, method, R) { if (!requireNamespace("poolr", quietly = TRUE)) { - msg <- glue( - "To use this method, please install poolr: ", - "install.packages('poolr')" - ) - abort(msg) + abort("Package 'poolr' is required for this method.") } fn <- switch( method, @@ -138,7 +134,7 @@ pvalPoolr <- function(pvals, method, R) { pvalGbj <- function(zScores, R, method) { if (!requireNamespace("GBJ", quietly = TRUE)) { - abort("To use this method, please install GBJ: install.packages('GBJ')") + abort("Package 'GBJ' is required for this method.") } result <- switch( method, @@ -165,7 +161,7 @@ pvalGbj <- function(zScores, R, method) { pvalAspu <- function(zScores = NULL, pvals = NULL, R, method) { if (!requireNamespace("aSPU", quietly = TRUE)) { abort( - "To use this method, please install aSPU: install.packages('aSPU')" + "Package 'aSPU' is required for this method." ) } switch( @@ -233,13 +229,12 @@ pvalAspu <- function(zScores = NULL, pvals = NULL, R, method) { # Internal: align an R correlation matrix to a target order. If R has # rownames/colnames, reorder to match `targetNames`; require every target # name to be present. If R is unnamed, only length check. +#' @importFrom checkmate assertMatrix .combinePvalAlignR <- function(R, targetNames) { if (is.null(R)) { return(NULL) } - if (!is.matrix(R)) { - abort("`R` must be a matrix.") - } + assertMatrix(R) if (nrow(R) != ncol(R)) { abort("`R` must be square.") } @@ -347,6 +342,7 @@ pvalAspu <- function(zScores = NULL, pvals = NULL, R, method) { #' @examples #' combinePValues(pvals = c(0.01, 0.2, 0.5), methods = "fisher", R = diag(3)) #' @export +#' @importFrom checkmate assertFlag combinePValues <- function( pvals = NULL, zScores = NULL, @@ -354,6 +350,7 @@ combinePValues <- function( R = NULL, naRm = TRUE ) { + assertFlag(naRm) methods <- .combinePvalCheckMethods(methods) nPvalsIn <- if (is.null(pvals)) 0L else length(pvals) nZScoresIn <- if (is.null(zScores)) 0L else length(zScores) @@ -517,13 +514,13 @@ combinePValues <- function( } # @noRd +#' @importFrom rlang try_fetch .combinePvalRunOne <- function(m, pvalsK, zScoresK, Raligned) { - p <- tryCatch( + p <- try_fetch( .combinePvalSingle(m, pvals = pvalsK, zScores = zScoresK, R = Raligned), - error = function(e) { - eMsg <- conditionMessage(e) - msg <- glue("combinePValues: method '{m}' failed: {eMsg}") - warn(msg) + error = function(cnd) { + msg <- glue("combinePValues: method '{m}' failed") + warn(msg, parent = cnd) NA_real_ } ) diff --git a/R/qtlAssociationPostprocess.R b/R/qtlAssociationPostprocess.R index 21bd39d41..8fef6bce2 100644 --- a/R/qtlAssociationPostprocess.R +++ b/R/qtlAssociationPostprocess.R @@ -23,21 +23,22 @@ # edge-case retries only (never a hand-rolled substitute): lambda=0 for # missing/infinite handling, bootstrap pi0 for the "pi0 <= 0" degenerate case. # Returns a numeric vector aligned to `p`. +#' @importFrom rlang try_fetch .qapSafeQvalue <- function(p) { if (!requireNamespace("qvalue", quietly = TRUE)) { # Optional-package guard; qvalue is Suggests-only. msg <- glue( "qtlAssociationPostprocess: the 'qvalue' package is required for ", - "Storey q-values. Install Bioconductor 'qvalue'." + "Storey q-values." ) abort(msg) } - tryCatch( + try_fetch( qvalue::qvalue(p)$qvalues, - error = function(e) { - if (str_detect(conditionMessage(e), "missing or infinite")) { + error = function(cnd) { + if (str_detect(conditionMessage(cnd), "missing or infinite")) { qvalue::qvalue(p, lambda = 0)$qvalues - } else if (str_detect(conditionMessage(e), "pi0 <= 0")) { + } else if (str_detect(conditionMessage(cnd), "pi0 <= 0")) { maxP <- max(p, na.rm = TRUE) lambdaSeq <- seq(0, min(0.9, maxP * 0.95), length.out = 10) qvalue::qvalue( @@ -46,12 +47,13 @@ pi0.method = "bootstrap" )$qvalues } else { + # The cause is chained via `parent`, so it is no longer + # interpolated into the message. msg <- glue( - "qtlAssociationPostprocess: qvalue::qvalue failed ", - "({conditionMessage(e)}). Not substituting a ", - "hand-rolled q-value." + "qtlAssociationPostprocess: qvalue::qvalue failed. ", + "Not substituting a hand-rolled q-value." ) - abort(msg) + abort(msg, parent = cnd) } } ) diff --git a/R/qtlEnrichmentPipeline.R b/R/qtlEnrichmentPipeline.R index 9519f616c..234b87515 100644 --- a/R/qtlEnrichmentPipeline.R +++ b/R/qtlEnrichmentPipeline.R @@ -55,7 +55,9 @@ #' @param seed Integer or \code{NULL}. Base random seed forwarded to #' \code{\link{qtlEnrichment}} for reproducible multiple imputation. #' \code{NULL} (default) draws a nondeterministic seed. -#' @param ... Additional arguments forwarded to \code{\link{qtlEnrichment}}. +#' @param verbose Logical. Print progress messages. Default \code{TRUE}. +#' @param enrichmentArgs Optional named list of options forwarded to +#' \code{\link{qtlEnrichment}}. #' @return A tibble with one row per (outcome trait, annotation unit) pair. #' The identity columns are \code{gwasStudy}, \code{gwasContext}, #' \code{gwasTrait}, \code{qtlStudy}, \code{qtlContext}; the axes a side does @@ -93,39 +95,46 @@ qtlEnrichmentPipeline <- function( impN = 25, numThreads = 1L, seed = NULL, - ... + verbose = TRUE, + enrichmentArgs = list() ) { .enrValidateInputs(gwasFineMappingResult, qtlFineMappingResult) - p <- as.list(environment()) - p$dots <- list(...) - p <- .enrPrepare(p) + prep <- .enrPrepare(gwasFineMappingResult, qtlFineMappingResult) + gwasTuples <- prep$gwasTuples + qtlTuples <- prep$qtlTuples + alignedByTuple <- prep$alignedByTuple results <- list_flatten(map( - seq_len(nrow(p$gwasTuples)), + seq_len(nrow(gwasTuples)), .enrScoreOutcomeTuple, - p = p + gwasPipByTuple = prep$gwasPipByTuple, + qtlRegionsByTuple = prep$qtlRegionsByTuple, + alignedByTuple = alignedByTuple, + numGwas = numGwas, + piQtl = piQtl, + lambda = lambda, + impN = impN, + numThreads = numThreads, + seed = seed, + verbose = verbose, + enrichmentArgs = enrichmentArgs, + gwasFineMappingResult = gwasFineMappingResult, + gwasTuples = gwasTuples, + qtlFineMappingResult = qtlFineMappingResult, + qtlTuples = qtlTuples )) .enrAssemble(results) } # Validate the input classes + LD-sketch presence / identity. # @noRd +#' @importFrom checkmate assertMultiClass .enrValidateInputs <- function(gwasFineMappingResult, qtlFineMappingResult) { - if (!methods::is(gwasFineMappingResult, "FineMappingResultBase")) { - msg <- glue( - "`gwasFineMappingResult` must be a GwasFineMappingResult or a ", - "QtlFineMappingResult ", - "(got class '{class(gwasFineMappingResult)[[1L]]}')." - ) - abort(msg) - } - if (!methods::is(qtlFineMappingResult, "FineMappingResultBase")) { - msg <- glue( - "`qtlFineMappingResult` must be a QtlFineMappingResult or a ", - "GwasFineMappingResult ", - "(got class '{class(qtlFineMappingResult)[[1L]]}')." - ) - abort(msg) - } + # assertMultiClass rather than assertClass on the virtual parent: naming + # both concrete subclasses is more use to a caller than + # "FineMappingResultBase", and these two are its only subclasses. + fmrClasses <- c("GwasFineMappingResult", "QtlFineMappingResult") + assertMultiClass(gwasFineMappingResult, fmrClasses) + assertMultiClass(qtlFineMappingResult, fmrClasses) outcomeLd <- getLdSketch(gwasFineMappingResult) if ( is.null(outcomeLd) && @@ -154,38 +163,41 @@ qtlEnrichmentPipeline <- function( # each tuple's one-time alignment to the union panel (errors captured as # values). # @noRd -.enrPrepare <- function(p) { - p$gwasTuples <- .enrOutcomeTuples(p$gwasFineMappingResult) - p$qtlTuples <- .enrAnnotationTuples(p$qtlFineMappingResult) - if (nrow(p$gwasTuples) == 0L || nrow(p$qtlTuples) == 0L) { +.enrPrepare <- function(gwasFineMappingResult, qtlFineMappingResult) { + gwasTuples <- .enrOutcomeTuples(gwasFineMappingResult) + qtlTuples <- .enrAnnotationTuples(qtlFineMappingResult) + if (nrow(gwasTuples) == 0L || nrow(qtlTuples) == 0L) { msg <- glue( "qtlEnrichmentPipeline: no (outcome, annotation) pairs to ", "compute (one of the inputs has zero rows)." ) abort(msg) } - p$gwasPipByTuple <- map( - seq_len(nrow(p$gwasTuples)), + gwasPipByTuple <- map( + seq_len(nrow(gwasTuples)), .enrGwasPipForRow, - gwasTuples = p$gwasTuples, - fmr = p$gwasFineMappingResult + gwasTuples = gwasTuples, + fmr = gwasFineMappingResult ) - unionGwasNames <- unique(unlist( - map(p$gwasPipByTuple, names), - use.names = FALSE - )) - p$qtlRegionsByTuple <- map( - seq_len(nrow(p$qtlTuples)), + unionGwasNames <- unique(unname(list_c(map(gwasPipByTuple, names)))) + qtlRegionsByTuple <- map( + seq_len(nrow(qtlTuples)), .enrQtlRegionsForRow, - qtlTuples = p$qtlTuples, - fmr = p$qtlFineMappingResult + qtlTuples = qtlTuples, + fmr = qtlFineMappingResult ) - p$alignedByTuple <- map( - p$qtlRegionsByTuple, + alignedByTuple <- map( + qtlRegionsByTuple, .enrAlignRegionsSafe, unionGwasNames = unionGwasNames ) - p + list( + gwasTuples = gwasTuples, + qtlTuples = qtlTuples, + gwasPipByTuple = gwasPipByTuple, + qtlRegionsByTuple = qtlRegionsByTuple, + alignedByTuple = alignedByTuple + ) } # The outcome side's per-trait keys: one PIP vector is built per key. A GWAS @@ -264,82 +276,167 @@ qtlEnrichmentPipeline <- function( # Align one tuple's regions to the union GWAS panel, capturing any error as a # value (re-raised + skipped per (gwas, tuple) below, never aborting). # @noRd +#' @importFrom rlang try_fetch .enrAlignRegionsSafe <- function(regions, unionGwasNames) { - tryCatch( + try_fetch( .enrAlignRegions(regions, unionGwasNames), - error = function(e) e + error = function(cnd) cnd ) } # Score one outcome trait against every annotation tuple -> enrichment records # (empty when the outcome has no usable PIPs). # @noRd -.enrScoreOutcomeTuple <- function(gi, p) { - gwasPip <- p$gwasPipByTuple[[gi]] +.enrScoreOutcomeTuple <- function( + gi, + gwasPipByTuple, + qtlRegionsByTuple, + alignedByTuple = alignedByTuple, + numGwas = numGwas, + piQtl = piQtl, + lambda = lambda, + impN = impN, + numThreads = numThreads, + seed = seed, + verbose = verbose, + enrichmentArgs = enrichmentArgs, + gwasFineMappingResult = gwasFineMappingResult, + gwasTuples = gwasTuples, + qtlFineMappingResult = qtlFineMappingResult, + qtlTuples = qtlTuples +) { + gwasPip <- gwasPipByTuple[[gi]] if (length(gwasPip) == 0L) { msg <- glue( "qtlEnrichmentPipeline: no usable PIPs for ", - "{.enrOutcomeLabel(p, gi)}; skipping." + "{.enrOutcomeLabel(gwasFineMappingResult, gwasTuples, gi)}; ", + "skipping." ) warn(msg) return(list()) } compact(map( - seq_len(nrow(p$qtlTuples)), + seq_len(nrow(qtlTuples)), .enrScoreTuple, gi = gi, gwasPip = gwasPip, - p = p + qtlRegionsByTuple = qtlRegionsByTuple, + alignedByTuple = alignedByTuple, + numGwas = numGwas, + piQtl = piQtl, + lambda = lambda, + impN = impN, + numThreads = numThreads, + seed = seed, + verbose = verbose, + enrichmentArgs = enrichmentArgs, + gwasFineMappingResult = gwasFineMappingResult, + gwasTuples = gwasTuples, + qtlFineMappingResult = qtlFineMappingResult, + qtlTuples = qtlTuples )) } # Score one (outcome trait, annotation tuple) pair -> an enrichment record, or # NULL when the tuple has no regions or qtlEnrichment fails. # @noRd -.enrScoreTuple <- function(k, gi, gwasPip, p) { - if (length(p$qtlRegionsByTuple[[k]]) == 0L) { +.enrScoreTuple <- function( + k, + gi, + gwasPip, + qtlRegionsByTuple, + alignedByTuple = alignedByTuple, + numGwas = numGwas, + piQtl = piQtl, + lambda = lambda, + impN = impN, + numThreads = numThreads, + seed = seed, + verbose = verbose, + enrichmentArgs = enrichmentArgs, + gwasFineMappingResult = gwasFineMappingResult, + gwasTuples = gwasTuples, + qtlFineMappingResult = qtlFineMappingResult, + qtlTuples = qtlTuples +) { + if (length(qtlRegionsByTuple[[k]]) == 0L) { msg <- glue( "qtlEnrichmentPipeline: no usable regions for ", - "{.enrAnnotationLabel(p, k)}; skipping." + "{.enrAnnotationLabel(qtlFineMappingResult, qtlTuples, k)}; ", + "skipping." ) warn(msg) return(NULL) } - enr <- .enrRunEnrichment(gi, gwasPip, k, p) + enr <- .enrRunEnrichment( + gi, + gwasPip, + k, + alignedByTuple = alignedByTuple, + numGwas = numGwas, + piQtl = piQtl, + lambda = lambda, + impN = impN, + numThreads = numThreads, + seed = seed, + verbose = verbose, + enrichmentArgs = enrichmentArgs, + gwasFineMappingResult = gwasFineMappingResult, + gwasTuples = gwasTuples, + qtlFineMappingResult = qtlFineMappingResult, + qtlTuples = qtlTuples + ) if (is.null(enr)) { return(NULL) } c( .enrFlattenEnrichment(enr), - as.list(p$gwasTuples[gi, , drop = FALSE]), - as.list(p$qtlTuples[k, , drop = FALSE]) + as.list(gwasTuples[gi, , drop = FALSE]), + as.list(qtlTuples[k, , drop = FALSE]) ) } # Human-readable identities for the warnings above, naming each side by its own # flavour and only the axes it has. # @noRd -.enrOutcomeLabel <- function(p, gi) { +.enrOutcomeLabel <- function(gwasFineMappingResult, gwasTuples, gi) { .fmrTupleLabel( - .fmrSideName(p$gwasFineMappingResult), - .enrOutcomeIdent(p$gwasTuples, gi) + .fmrSideName(gwasFineMappingResult), + .enrOutcomeIdent(gwasTuples, gi) ) } # @noRd -.enrAnnotationLabel <- function(p, k) { +.enrAnnotationLabel <- function(qtlFineMappingResult, qtlTuples, k) { .fmrTupleLabel( - .fmrSideName(p$qtlFineMappingResult), - .enrAnnotationIdent(p$qtlTuples, k) + .fmrSideName(qtlFineMappingResult), + .enrAnnotationIdent(qtlTuples, k) ) } # Run qtlEnrichment for a pair (with the pre-aligned regions), warning + NULL on # failure. alignNames = FALSE reuses the shared per-tuple alignment. # @noRd -.enrRunEnrichment <- function(gi, gwasPip, k, p) { - aligned <- p$alignedByTuple[[k]] - tryCatch( +.enrRunEnrichment <- function( + gi, + gwasPip, + k, + alignedByTuple, + numGwas, + piQtl, + lambda, + impN, + numThreads, + seed, + verbose, + enrichmentArgs, + gwasFineMappingResult, + gwasTuples, + qtlFineMappingResult, + qtlTuples +) { + aligned <- alignedByTuple[[k]] + try_fetch( { if (inherits(aligned, "condition")) { cnd_signal(aligned) @@ -348,24 +445,26 @@ qtlEnrichmentPipeline <- function( list( gwasPip = gwasPip, susieQtlRegions = aligned, - numGwas = p$numGwas, - piQtl = p$piQtl, - lambda = p$lambda, - impN = p$impN, - numThreads = p$numThreads, - seed = p$seed, + numGwas = numGwas, + piQtl = piQtl, + lambda = lambda, + impN = impN, + numThreads = numThreads, + seed = seed, + verbose = verbose, alignNames = FALSE ), - p$dots + enrichmentArgs ) exec(qtlEnrichment, !!!enrichArgs) }, - error = function(e) { - eMsg <- conditionMessage(e) + error = function(cnd) { + eMsg <- conditionMessage(cnd) msg <- glue( "qtlEnrichmentPipeline: qtlEnrichment failed for ", - "{.enrOutcomeLabel(p, gi)} x ", - "{.enrAnnotationLabel(p, k)}: {eMsg}" + "{.enrOutcomeLabel(gwasFineMappingResult, gwasTuples, gi)} x ", + "{.enrAnnotationLabel(qtlFineMappingResult, qtlTuples, k)}: ", + "{eMsg}" ) warn(msg) NULL @@ -453,7 +552,7 @@ qtlEnrichmentPipeline <- function( if (length(pieces) == 0L) { return(numeric(0)) } - all <- unlist(pieces) + all <- list_c(pieces) if (n_distinct(names(all)) < length(all)) { all <- .enrCollapseDuplicatePips(all) } @@ -776,7 +875,7 @@ qtlEnrichment <- function( "variables inside of credible sets or signal clusters." ) warn(msg) - allPips <- unlist(map(susieQtlRegions, "pip")) + allPips <- list_c(map(susieQtlRegions, "pip")) piQtl <- sum(allPips) / length(allPips) if (verbose) { piQtlR <- round(piQtl, 5) diff --git a/R/qtlSumStats.R b/R/qtlSumStats.R index a962b5f06..add756be6 100644 --- a/R/qtlSumStats.R +++ b/R/qtlSumStats.R @@ -36,39 +36,25 @@ setClass( # Collect all contract violations (empty vector = valid). The per-entry checks # run only once the basic slot/column checks pass (they assume those columns). # @noRd +#' @importFrom checkmate makeAssertCollection assertNames .validateQtlSumStats <- function(object) { - errors <- c( - .qssCheckLdSketch(object), - .qssCheckRequiredCols(object), - .qssCheckGenome(object), - .qssCheckQcInfo(object), - .validateTraitPosColumn(object) + coll <- makeAssertCollection() + # `names(object)` is element names on a RangedTupleList, so the required + # metadata columns are read from mcols directly. + assertNames( + colnames(mcols(object)) %||% character(0), + must.include = c("study", "context", "trait"), + what = "colnames", + .var.name = "mcols", + add = coll ) - if (length(errors) == 0L) { - errors <- .qssCheckEntries(object) + coll$push(.qssCheckGenome(object)) + coll$push(.validateTraitPosColumn(object)) + if (!coll$isEmpty()) { + return(coll$getMessages()) } - if (length(errors) == 0L) TRUE else errors -} - -# ldSketch must be a GenotypeHandle or NULL. -# @noRd -.qssCheckLdSketch <- function(object) { - # The slot's class union enforces the type; nothing to check. - NULL -} - -# The study/context/trait metadata columns must be present. `names(object)` is -# element names on a RangedTupleList, so the check reads mcols directly. -# @noRd -.qssCheckRequiredCols <- function(object) { - missingCols <- setdiff( - c("study", "context", "trait"), - colnames(mcols(object)) - ) - if (length(missingCols) > 0L) { - return(str_c("missing columns: ", str_flatten(missingCols, ", "))) - } - NULL + coll$push(.qssCheckEntries(object)) + coll$getMessages() } # The genome build, read from seqinfo (there is no genome slot). @@ -77,13 +63,6 @@ setClass( .sumStatsCheckGenome(object) } -# qcInfo slot must be a list. -# @noRd -.qssCheckQcInfo <- function(object) { - # The slot's declared type enforces this; nothing to check. - NULL -} - # Element contract. The elements ARE GRanges by construction now -- the # container is a GRangesList -- and RangedTupleList's validity enforces the # one-seqname/one-strand invariant, so only tuple uniqueness is left to check. @@ -321,6 +300,7 @@ QtlSumStats <- function( # Internal: resolve a (study, context, trait) tuple to its element indices. # Returns a VECTOR: a tuple whose entry spanned several chromosomes was split # into one element per seqname at construction. +#' @importFrom checkmate assertVector .qtlSumStatsSelectRow <- function(x, study, context, trait) { if (nrow(x) == 0L) { abort("QtlSumStats has no rows.") @@ -341,9 +321,9 @@ QtlSumStats <- function( ) abort(msg) } - if (length(study) != 1L || length(context) != 1L || length(trait) != 1L) { - abort("`study`, `context`, and `trait` must each be length 1.") - } + assertVector(study, len = 1L) + assertVector(context, len = 1L) + assertVector(trait, len = 1L) .qssMatchTuple(x, study, context, trait) } diff --git a/R/regularizedRegressionWrappers.R b/R/regularizedRegressionWrappers.R index 39dbdd039..a84f35f61 100644 --- a/R/regularizedRegressionWrappers.R +++ b/R/regularizedRegressionWrappers.R @@ -11,7 +11,8 @@ #' @param w0 Numeric vector of prior mixture weights (summing to 1). #' @param z Optional numeric vector of z-scores; defaults to \code{numeric(0)} #' (derived from \code{stat}). -#' @param ... Additional arguments forwarded to \code{mr.ash.rss}. +#' @param methodArgs Optional named list of options forwarded to +#' \code{mr.ash.rss}. #' @return A numeric vector of the posterior mean of the coefficients. #' @importFrom susieR mr.ash.rss #' @examples @@ -25,6 +26,7 @@ #' seb = vapply(ss, `[`, numeric(1), 2L), n = rep(nrow(X), ncol(X))) #' mrashRssWeights(stat, cor(X), varY = var(y), sigma2E = var(y), #' s0 = c(0, 0.1, 0.5), w0 = c(0.8, 0.1, 0.1)) +#' @importFrom checkmate assertList assertNumeric #' @export mrashRssWeights <- function( stat, @@ -34,20 +36,25 @@ mrashRssWeights <- function( s0, w0, z = numeric(0), - ... + methodArgs = list() ) { - model <- mr.ash.rss( - bhat = stat$b, - shat = stat$seb, - z = z, - R = LD, - var_y = varY, - n = median(stat$n), - sigma2_e = sigma2E, - s0 = s0, - w0 = w0, - ... + assertList(stat) + assertNumeric(z) + callArgs <- list_modify( + list( + bhat = stat$b, + shat = stat$seb, + z = z, + R = LD, + var_y = varY, + n = median(stat$n), + sigma2_e = sigma2E, + s0 = s0, + w0 = w0 + ), + !!!methodArgs ) + model <- exec(mr.ash.rss, !!!callArgs) return(model$mu1) } @@ -174,7 +181,7 @@ prsCs <- function( #' sizes) and \code{n} (per-variant sample sizes). #' @param LD Numeric LD (correlation) matrix aligned to the variants in #' \code{stat}. -#' @param ... Additional arguments forwarded to \code{prsCs}. +#' @param methodArgs Optional named list of options forwarded to \code{prsCs}. #' @return A numeric vector of the posterior SNP coefficients. #' @examples #' data(eqtlRegionExample) @@ -190,8 +197,12 @@ prsCs <- function( #' LD <- cor(X) #' prsCsWeights(stat, LD, maf = rep(0.3, ncol(X))) #' @export -prsCsWeights <- function(stat, LD, ...) { - model <- prsCs(bhat = stat$b, R = LD, n = median(stat$n), ...) +prsCsWeights <- function(stat, LD, methodArgs = list()) { + callArgs <- list_modify( + list(bhat = stat$b, R = LD, n = median(stat$n)), + !!!methodArgs + ) + model <- exec(prsCs, !!!callArgs) return(model$betaEst) } @@ -349,7 +360,7 @@ sdpr <- function( #' sizes) and \code{n} (per-variant sample sizes). #' @param LD Numeric LD (correlation) matrix aligned to the variants in #' \code{stat}. -#' @param ... Additional arguments forwarded to \code{sdpr}. +#' @param methodArgs Optional named list of options forwarded to \code{sdpr}. #' @return A numeric vector of the posterior SNP coefficients. #' @examples #' data(eqtlRegionExample) @@ -365,8 +376,12 @@ sdpr <- function( #' LD <- cor(X) #' sdprWeights(stat, LD) #' @export -sdprWeights <- function(stat, LD, ...) { - model <- sdpr(bhat = stat$b, R = LD, n = median(stat$n), ...) +sdprWeights <- function(stat, LD, methodArgs = list()) { + callArgs <- list_modify( + list(bhat = stat$b, R = LD, n = median(stat$n)), + !!!methodArgs + ) + model <- exec(sdpr, !!!callArgs) return(model$betaEst) } @@ -391,7 +406,8 @@ sdprWeights <- function(stat, LD, ...) { #' is not duplicated. `"full"` additionally retains the complete mr.mash fit #' under `$fit` (consistent with how susie fits are kept), at the cost of a #' larger payload. -#' @param ... Additional arguments passed to `mrmashWrapper()` when fitting. +#' @param methodArgs Optional named list of options passed to +#' `mrmashWrapper()` when fitting. #' @return Matrix of variant weights. #' @examples #' data(multiTraitData) @@ -401,6 +417,9 @@ sdprWeights <- function(stat, LD, ...) { #' fit <- mrmashWrapper(X = X, Y = Y, dataDrivenPriorMatrices = ddpm, #' canonicalPriorMatrices = TRUE) #' mrmashWeights(mrmashFit = fit, X = X, Y = Y) +#' @param dataDrivenPriorMatrices Optional list of data-driven prior +#' covariance matrices; forwarded to \code{mrmashWrapper} when it has to fit, +#' and retained in the payload for mvSuSiE prior reconstruction. #' @export mrmashWeights <- function( mrmashFit = NULL, @@ -408,22 +427,24 @@ mrmashWeights <- function( Y = NULL, retainFit = FALSE, fitDetail = c("slim", "full"), - ... + dataDrivenPriorMatrices = NULL, + methodArgs = list() ) { if (!requireNamespace("mr.mashr", quietly = TRUE)) { - msg <- glue( - "Package 'mr.mashr' is required. Install with: ", - "devtools::install_github('stephenslab/mr.mashr')" - ) - abort(msg) + abort("Package 'mr.mashr' is required.") } - dotArgs <- list(...) if (is.null(mrmashFit)) { inform("mrmashFit is not provided; fitting mr.mash now ...") if (is.null(X) || is.null(Y)) { abort("Both X and Y must be provided if mrmashFit is NULL.") } - mrmashFit <- mrmashWrapper(X, Y, ...) + mrmashFit <- exec( + mrmashWrapper, + X, + Y, + dataDrivenPriorMatrices = dataDrivenPriorMatrices, + !!!methodArgs + ) } out <- mr.mashr::coef.mr.mash(mrmashFit)[-1, ] # mu1 (= out) is already the returned weights; the payload carries only the @@ -432,7 +453,7 @@ mrmashWeights <- function( .mrmashAttachFit( out, mrmashFit, - dotArgs$dataDrivenPriorMatrices, + dataDrivenPriorMatrices, retainFit, fitDetail ) @@ -516,7 +537,8 @@ mrmashWeights <- function( #' coefficients are already the returned weights); \code{"full"} additionally #' keeps the complete \code{mr.mash.rss} fit under \code{$fit}. Mirrors #' \code{\link{mrmashWeights}}. -#' @param ... Additional arguments forwarded to \code{mr.mashr::mr.mash.rss}. +#' @param methodArgs Optional named list of options forwarded to +#' \code{mr.mashr::mr.mash.rss}. #' #' @return A numeric matrix of per-variant per-context weights (variants x #' conditions). @@ -532,6 +554,7 @@ mrmashWeights <- function( #' dataDrivenPriorMatrices = multiTraitData$priorMatrices, #' canonicalPriorMatrices = TRUE) #' @export +#' @importFrom checkmate assertFlag assertList mrmashRssWeights <- function( stat, LD, @@ -544,8 +567,11 @@ mrmashRssWeights <- function( covY = NULL, retainFit = FALSE, fitDetail = c("slim", "full"), - ... + methodArgs = list() ) { + assertList(stat) + assertFlag(canonicalPriorMatrices) + assertFlag(retainFit) .mrmashRssRequirePackage() if (is.null(mrmashRssFit)) { mrmashRssFit <- .mrmashRssComputeFit( @@ -557,7 +583,7 @@ mrmashRssWeights <- function( w0, V, covY, - list(...) + methodArgs ) } # coef.mr.mash.rss returns nrow(Bhat) rows (no intercept). Do not strip. @@ -577,8 +603,7 @@ mrmashRssWeights <- function( if (!requireNamespace("mr.mashr", quietly = TRUE)) { msg <- glue( "Package 'mr.mashr' is required. ", - "Install with: ", - "devtools::install_github('stephenslab/mr.mash.alpha')" + "is required." ) abort(msg) } @@ -711,11 +736,7 @@ initPriorSd <- function(X, y, n = 30) { glmnetWeights <- function(X, y, alpha) { # Check if glmnet is installed if (!requireNamespace("glmnet", quietly = TRUE)) { - msg <- glue( - "To use this function, please install glmnet: ", - "https://cran.r-project.org/web/packages/glmnet/index.html" - ) - abort(msg) + abort("Package 'glmnet' is required for this function.") } eff.wgt <- matrix(0, ncol = 1, nrow = ncol(X)) keep <- .dropZeroVariance(X, "glmnetWeights") @@ -777,16 +798,25 @@ lassoWeights <- function(X, y) glmnetWeights(X, y, 1) #' from the data. Default \code{TRUE}. #' @param retainFit Logical. Attach the full fitted-model object to the result. #' Default \code{FALSE}. -#' @param ... Additional arguments forwarded to \code{mr.ash}. +#' @param methodArgs Optional named list of options forwarded to \code{mr.ash}. #' @return A numeric vector of weights, one per variant (column of \code{X}); #' zero-variance columns receive weight 0. When \code{retainFit = TRUE} the #' fitted \code{mr.ash} object is attached as attribute \code{"fit"}. +#' @importFrom checkmate assertFlag #' @export -mrashWeights <- function(X, y, initPriorSd = TRUE, retainFit = FALSE, ...) { +mrashWeights <- function( + X, + y, + initPriorSd = TRUE, + retainFit = FALSE, + methodArgs = list() +) { + assertFlag(initPriorSd) + assertFlag(retainFit) eff.wgt <- rep(0, ncol(X)) keep <- .dropZeroVariance(X, "mrashWeights") XKeep <- X[, keep, drop = FALSE] - argsList <- list(...) + argsList <- methodArgs if (!is_in("beta.init", names(argsList))) { argsList$beta.init <- lassoWeights(XKeep, y) } else if (length(argsList$beta.init) == ncol(X)) { @@ -824,7 +854,8 @@ mrashWeights <- function(X, y, initPriorSd = TRUE, retainFit = FALSE, ...) { #' \code{1000}. #' @param nthin Integer. Thinning interval for retained MCMC samples. Default #' \code{5}. -#' @param ... Additional arguments forwarded to \code{qgg::gbayes}. +#' @param methodArgs Optional named list of options forwarded to +#' \code{qgg::gbayes}. #' @return A vector containing the weights to be applied to each genotype in #' predicting the phenotype. #' @details This function fits a Bayesian linear regression model with a range @@ -850,23 +881,26 @@ bayesAlphabetWeights <- function( nit = 5000, nburn = 1000, nthin = 5, - ... + methodArgs = list() ) { .bayesAlphabetValidate(X, y, Z) eff.wgt <- rep(0, ncol(X)) keep <- .dropZeroVariance(X, "bayesAlphabetWeights") - model <- qgg::gbayes( - y = y, - W = X[, keep, drop = FALSE], - X = Z, - method = method, - h2 = h2, - nit = nit, - nburn = nburn, - ... + callArgs <- list_modify( + list( + y = y, + W = X[, keep, drop = FALSE], + X = Z, + method = method, + h2 = h2, + nit = nit, + nburn = nburn + ), + !!!methodArgs ) + model <- exec(qgg::gbayes, !!!callArgs) eff.wgt[keep] <- model$bm return(eff.wgt) @@ -875,22 +909,13 @@ bayesAlphabetWeights <- function( # Shared input validation for the gbayes-backed weight fitters: qgg present, # and matching row counts for response / genotype / covariates. # @noRd +#' @importFrom checkmate assertMatrix assertVector .bayesAlphabetValidate <- function(X, y, Z) { if (!requireNamespace("qgg", quietly = TRUE)) { - msg <- glue( - "To use this function, please install qgg: ", - "https://cran.r-project.org/web/packages/qgg/index.html" - ) - abort(msg) - } - if (!(length(y) == nrow(X))) { - abort("All objects must have the same number of rows") - } - if (!is.null(Z) && nrow(X) != nrow(Z)) { - abort( - "Genotype and covariate matrices must have same number of rows" - ) + abort("Package 'qgg' is required for this function.") } + assertVector(y, len = nrow(X)) + assertMatrix(Z, nrows = nrow(X), null.ok = TRUE) } #' @title BayesN TWAS weights (Gaussian prior, ridge-equivalent) #' @description Use Gaussian distribution as prior. Posterior means will be @@ -898,7 +923,8 @@ bayesAlphabetWeights <- function( #' @param X Numeric genotype / design matrix (samples x variants). #' @param y Numeric response (phenotype) vector of length \code{nrow(X)}. #' @param Z Optional numeric matrix of fixed-effect covariates, or \code{NULL}. -#' @param ... Additional arguments forwarded to \code{bayesAlphabetWeights} / +#' @param methodArgs Optional named list of options forwarded to +#' \code{bayesAlphabetWeights} / #' \code{qgg}. #' @return A numeric vector of effect-size weights, one per variant (column of #' \code{X}); columns dropped for zero variance receive weight 0. @@ -908,8 +934,8 @@ bayesAlphabetWeights <- function( #' y <- eqtlRegionExample$yRes #' bayesNWeights(X, y) #' @export -bayesNWeights <- function(X, y, Z = NULL, ...) { - return(bayesAlphabetWeights(X, y, method = "bayesN", Z, ...)) +bayesNWeights <- function(X, y, Z = NULL, methodArgs = list()) { + bayesAlphabetWeights(X, y, method = "bayesN", Z, methodArgs = methodArgs) } #' @title BayesL TWAS weights (Laplace prior, LASSO-equivalent) #' @description Use laplace/double exponential distribution as prior. This is @@ -917,7 +943,8 @@ bayesNWeights <- function(X, y, Z = NULL, ...) { #' @param X Numeric genotype / design matrix (samples x variants). #' @param y Numeric response (phenotype) vector of length \code{nrow(X)}. #' @param Z Optional numeric matrix of fixed-effect covariates, or \code{NULL}. -#' @param ... Additional arguments forwarded to \code{bayesAlphabetWeights} / +#' @param methodArgs Optional named list of options forwarded to +#' \code{bayesAlphabetWeights} / #' \code{qgg}. #' @return A numeric vector of effect-size weights, one per variant (column of #' \code{X}); columns dropped for zero variance receive weight 0. @@ -927,15 +954,16 @@ bayesNWeights <- function(X, y, Z = NULL, ...) { #' y <- eqtlRegionExample$yRes #' bayesLWeights(X, y) #' @export -bayesLWeights <- function(X, y, Z = NULL, ...) { - return(bayesAlphabetWeights(X, y, method = "bayesL", Z, ...)) +bayesLWeights <- function(X, y, Z = NULL, methodArgs = list()) { + bayesAlphabetWeights(X, y, method = "bayesL", Z, methodArgs = methodArgs) } #' @title BayesA TWAS weights (t-distribution prior) #' @description Use t-distribution as prior. #' @param X Numeric genotype / design matrix (samples x variants). #' @param y Numeric response (phenotype) vector of length \code{nrow(X)}. #' @param Z Optional numeric matrix of fixed-effect covariates, or \code{NULL}. -#' @param ... Additional arguments forwarded to \code{bayesAlphabetWeights} / +#' @param methodArgs Optional named list of options forwarded to +#' \code{bayesAlphabetWeights} / #' \code{qgg}. #' @return A numeric vector of effect-size weights, one per variant (column of #' \code{X}); columns dropped for zero variance receive weight 0. @@ -945,8 +973,8 @@ bayesLWeights <- function(X, y, Z = NULL, ...) { #' y <- eqtlRegionExample$yRes #' bayesAWeights(X, y) #' @export -bayesAWeights <- function(X, y, Z = NULL, ...) { - return(bayesAlphabetWeights(X, y, method = "bayesA", Z, ...)) +bayesAWeights <- function(X, y, Z = NULL, methodArgs = list()) { + bayesAlphabetWeights(X, y, method = "bayesA", Z, methodArgs = methodArgs) } #' @title BayesC TWAS weights (rounded-spike prior) #' @description Use a rounded spike prior (low-variance Gaussian). @@ -955,7 +983,8 @@ bayesAWeights <- function(X, y, Z = NULL, ...) { #' @param Z Optional numeric matrix of fixed-effect covariates, or \code{NULL}. #' @param pi Numeric in (0, 1). Prior proportion of non-null effects for the #' BayesC mixture. Default \code{0.1}. -#' @param ... Additional arguments forwarded to \code{bayesAlphabetWeights} / +#' @param methodArgs Optional named list of options forwarded to +#' \code{bayesAlphabetWeights} / #' \code{qgg}. #' @return A numeric vector of effect-size weights, one per variant (column of #' \code{X}); columns dropped for zero variance receive weight 0. @@ -965,8 +994,16 @@ bayesAWeights <- function(X, y, Z = NULL, ...) { #' y <- eqtlRegionExample$yRes #' bayesCWeights(X, y) #' @export -bayesCWeights <- function(X, y, Z = NULL, pi = 0.1, ...) { - return(bayesAlphabetWeights(X, y, method = "bayesC", Z, pi = pi, ...)) +bayesCWeights <- function(X, y, Z = NULL, pi = 0.1, methodArgs = list()) { + # `pi` is a qgg option, not a formal of bayesAlphabetWeights, so it joins + # the option list rather than the argument list. + bayesAlphabetWeights( + X, + y, + method = "bayesC", + Z, + methodArgs = c(list(pi = pi), methodArgs) + ) } #' @title BayesR TWAS weights (hierarchical mixture prior) #' @description Use a hierarchical Bayesian mixture model with four Gaussian @@ -974,7 +1011,8 @@ bayesCWeights <- function(X, y, Z = NULL, pi = 0.1, ...) { #' @param X Numeric genotype / design matrix (samples x variants). #' @param y Numeric response (phenotype) vector of length \code{nrow(X)}. #' @param Z Optional numeric matrix of fixed-effect covariates, or \code{NULL}. -#' @param ... Additional arguments forwarded to \code{bayesAlphabetWeights} / +#' @param methodArgs Optional named list of options forwarded to +#' \code{bayesAlphabetWeights} / #' \code{qgg}. #' @return A numeric vector of effect-size weights, one per variant (column of #' \code{X}); columns dropped for zero variance receive weight 0. @@ -984,8 +1022,8 @@ bayesCWeights <- function(X, y, Z = NULL, pi = 0.1, ...) { #' y <- eqtlRegionExample$yRes #' bayesRWeights(X, y) #' @export -bayesRWeights <- function(X, y, Z = NULL, ...) { - return(bayesAlphabetWeights(X, y, method = "bayesR", Z, ...)) +bayesRWeights <- function(X, y, Z = NULL, methodArgs = list()) { + bayesAlphabetWeights(X, y, method = "bayesR", Z, methodArgs = methodArgs) } @@ -1126,7 +1164,7 @@ bayesRWeights <- function(X, y, Z = NULL, ...) { # stop( # paste0( # "To use this function, please install qgg: ", -# "https://cran.r-project.org/web/packages/qgg/index.html" +# # ) # ) # } @@ -1376,30 +1414,30 @@ bayesRWeights <- function(X, y, Z = NULL, ...) { # #' Use Gaussian distribution as prior. Posterior means will be BLUP, # #' equivalent to Ridge Regression. # #' @export -# bayes_n_rss_weights <- function(sumstats, LD, ...) { +# bayes_n_rss_weights <- function(sumstats, LD, methodArgs = list()) { # return(bayes_alphabet_rss_weights(sumstats, LD, method = "bayesN", ...)) # } # #' Use laplace/double exponential distribution as prior. This is equivalent # #' to Bayesian LASSO. # #' @export -# bayes_l_rss_weights <- function(sumstats, LD, ...) { +# bayes_l_rss_weights <- function(sumstats, LD, methodArgs = list()) { # return(bayes_alphabet_rss_weights(sumstats, LD, method = "bayesL", ...)) # } # #' Use t-distribution as prior. # #' @export -# bayes_a_rss_weights <- function(sumstats, LD, ...) { +# bayes_a_rss_weights <- function(sumstats, LD, methodArgs = list()) { # return(bayes_alphabet_rss_weights(sumstats, LD, method = "bayesA", ...)) # } # #' Use a rounded spike prior (low-variance Gaussian). # #' @export -# bayes_c_rss_weights <- function(sumstats, LD, ...) { +# bayes_c_rss_weights <- function(sumstats, LD, methodArgs = list()) { # return(bayes_alphabet_rss_weights(sumstats, LD, method = "bayesC", ...)) # } # #' Use a hierarchical Bayesian mixture model with four Gaussian components. # #' Variances are scaled # #' by 0, 0.0001 , 0.001 , and 0.01 . # #' @export -# bayes_r_rss_weights <- function(sumstats, LD, ...) { +# bayes_r_rss_weights <- function(sumstats, LD, methodArgs = list()) { # return(bayes_alphabet_rss_weights(sumstats, LD, method = "bayesR", ...)) # } @@ -1444,6 +1482,7 @@ bayesRWeights <- function(X, y, Z = NULL, ...) { #' } #' out <- lassosumRss(bhat, R, n) #' @export +#' @importFrom checkmate assertCount assertNumber assertNumeric lassosumRss <- function( bhat, R, @@ -1452,6 +1491,9 @@ lassosumRss <- function( thr = 1e-4, maxiter = 10000 ) { + assertNumeric(lambda, lower = 0, any.missing = FALSE) + assertNumber(thr, lower = 0, finite = TRUE) + assertCount(maxiter, positive = TRUE) # cpp11 requires exact integer types; the C++ backend takes a block list, so # the single-window matrix R is wrapped as one block here. .rssSolvePath( @@ -1460,8 +1502,7 @@ lassosumRss <- function( n, lambda, .rssLassosumSolve, - thr = thr, - maxiter = maxiter + solveArgs = list(thr = thr, maxiter = maxiter) ) } @@ -1533,6 +1574,7 @@ lassosumRss <- function( # of an "argument is missing" error, and propagate through the public wrappers # (verified two levels deep). Method-specific checks -- prsCs's maf length, # sdpr's M / perVariantSampleSize / array -- stay in the caller. +#' @importFrom checkmate assertVector .rssValidateInputs <- function(bhat, R, n) { if (missing(R) || !is.matrix(R)) { abort("Please provide the LD correlation matrix 'R' as a matrix.") @@ -1540,9 +1582,7 @@ lassosumRss <- function( if (missing(n) || n <= 0) { abort("Please provide a valid sample size using 'n'.") } - if (length(bhat) != nrow(R)) { - abort("The length of 'bhat' must equal the number of rows of 'R'.") - } + assertVector(bhat, len = nrow(R)) invisible(NULL) } @@ -1551,11 +1591,14 @@ lassosumRss <- function( # lambda order via the inverse permutation and assemble the standard result # list. Shared by lassosumRss and penalizedRss, which differ only in which Rcpp # solver they pass as `solveFn` (and penalizedRss's per-penalty gamma default). -.rssSolvePath <- function(bhat, R, n, lambda, solveFn, ...) { +.rssSolvePath <- function(bhat, R, n, lambda, solveFn, solveArgs = list()) { .rssValidateInputs(bhat, R, n) z <- bhat / sqrt(n) order <- order(lambda, decreasing = TRUE) - result <- solveFn(z, lambda[order], R, ...) + # `solveArgs` rather than `...`: the two solvers take different fixed + # argument sets, and both callers know theirs statically, so an unknown + # solver argument should be an error here rather than reaching the solver. + result <- exec(solveFn, z, lambda[order], R, !!!solveArgs) # Reorder back to original lambda order via the inverse permutation. invOrder <- order(order) result$beta <- result$beta[, invOrder, drop = FALSE] @@ -1740,7 +1783,8 @@ lassosumRss <- function( #' @param selection Selection strategy. Default \code{"ldQuadratic"} uses #' \eqn{c^T \beta / \sqrt{\beta^T R \beta}} on the supplied LD matrix. #' \code{"minFbeta"} is retained as an explicit alternative for debugging. -#' @param ... Additional arguments passed to \code{lassosumRss()}. +#' @param methodArgs Optional named list of options passed to +#' \code{lassosumRss()}. #' #' @return A numeric vector of the posterior SNP coefficients at the best (s, #' lambda). @@ -1763,7 +1807,7 @@ lassosumRssWeights <- function( LD, s = c(0.2, 0.5, 0.9, 1.0), selection = c("ldQuadratic", "minFbeta"), - ... + methodArgs = list() ) { selection <- arg_match(selection) .rssShrinkGridWeights( @@ -1771,7 +1815,7 @@ lassosumRssWeights <- function( LD, s, "lassosum", - list(dotArgs = list(...)), + list(dotArgs = methodArgs), selection ) } @@ -1858,14 +1902,16 @@ penalizedRss <- function( n, lambda, .rssPenalizedSolve, - penalty = penalty, - gamma = gamma, - alpha = alpha, - lambda0 = lambda0, - lambda2 = lambda2, - thr = thr, - maxiter = maxiter, - maxSwaps = maxSwaps + solveArgs = list( + penalty = penalty, + gamma = gamma, + alpha = alpha, + lambda0 = lambda0, + lambda2 = lambda2, + thr = thr, + maxiter = maxiter, + maxSwaps = maxSwaps + ) ) } @@ -1878,8 +1924,10 @@ penalizedRss <- function( #' sI}) and selects the best candidate via LD-quadratic pseudovalidation or #' minimum penalized objective. #' -#' @param stat,LD,s,selection,penalty,gamma,alpha,lambda0,lambda2,... See the +#' @param stat,LD,s,selection,penalty,gamma,alpha,lambda0,lambda2 See the #' public wrappers for details. +#' @param methodArgs Optional named list of options forwarded to +#' \code{penalizedRss()}. #' @return Numeric weight vector of length \code{nrow(LD)}. #' @keywords internal .penalizedRssWeights <- function( @@ -1892,7 +1940,7 @@ penalizedRss <- function( lambda0 = 0, lambda2 = 0, selection = c("ldQuadratic", "minFbeta"), - ... + methodArgs = list() ) { selection <- arg_match(selection) .rssShrinkGridWeights( @@ -1906,7 +1954,7 @@ penalizedRss <- function( alpha = alpha, lambda0 = lambda0, lambda2 = lambda2, - dotArgs = list(...) + dotArgs = methodArgs ), selection ) @@ -1927,7 +1975,8 @@ penalizedRss <- function( #' @param alpha Elastic-net mixing (1 = pure L1). Default 1. #' @param selection Selection strategy: \code{"ldQuadratic"} (default) or #' \code{"minFbeta"}. -#' @param ... Additional arguments passed to \code{penalizedRss()}. +#' @param methodArgs Optional named list of options passed to +#' \code{penalizedRss()}. #' @return A numeric vector of SNP coefficient weights. #' @examples #' data(eqtlRegionExample) @@ -1942,6 +1991,7 @@ penalizedRss <- function( #' ) #' LD <- cor(X) #' scadRssWeights(stat, LD) +#' @importFrom checkmate assertList assertNumeric assertNumber #' @export scadRssWeights <- function( stat, @@ -1950,8 +2000,12 @@ scadRssWeights <- function( gamma = 3.7, alpha = 1.0, selection = c("ldQuadratic", "minFbeta"), - ... + methodArgs = list() ) { + assertList(stat) + assertNumeric(s, lower = 0, any.missing = FALSE) + assertNumber(gamma, finite = TRUE) + assertNumber(alpha, finite = TRUE) .penalizedRssWeights( stat = stat, LD = LD, @@ -1960,7 +2014,7 @@ scadRssWeights <- function( gamma = gamma, alpha = alpha, selection = selection, - ... + methodArgs = methodArgs ) } @@ -1979,7 +2033,8 @@ scadRssWeights <- function( #' @param alpha Elastic-net mixing (1 = pure L1). Default 1. #' @param selection Selection strategy: \code{"ldQuadratic"} (default) or #' \code{"minFbeta"}. -#' @param ... Additional arguments passed to \code{penalizedRss()}. +#' @param methodArgs Optional named list of options passed to +#' \code{penalizedRss()}. #' @return A numeric vector of SNP coefficient weights. #' @examples #' data(eqtlRegionExample) @@ -1994,6 +2049,7 @@ scadRssWeights <- function( #' ) #' LD <- cor(X) #' mcpRssWeights(stat, LD) +#' @importFrom checkmate assertList assertNumeric assertNumber #' @export mcpRssWeights <- function( stat, @@ -2002,8 +2058,12 @@ mcpRssWeights <- function( gamma = 3.0, alpha = 1.0, selection = c("ldQuadratic", "minFbeta"), - ... + methodArgs = list() ) { + assertList(stat) + assertNumeric(s, lower = 0, any.missing = FALSE) + assertNumber(gamma, finite = TRUE) + assertNumber(alpha, finite = TRUE) .penalizedRssWeights( stat = stat, LD = LD, @@ -2012,7 +2072,7 @@ mcpRssWeights <- function( gamma = gamma, alpha = alpha, selection = selection, - ... + methodArgs = methodArgs ) } @@ -2041,7 +2101,8 @@ mcpRssWeights <- function( #' @param selection Selection strategy: \code{"ldQuadratic"} (default) or #' \code{"minFbeta"}. #' @param maxSwaps Maximum swap rounds per lambda. Default 100. -#' @param ... Additional arguments passed to \code{penalizedRss()}. +#' @param methodArgs Optional named list of options passed to +#' \code{penalizedRss()}. #' @return A numeric vector of SNP coefficient weights. #' @examples #' data(eqtlRegionExample) @@ -2067,7 +2128,7 @@ l0learnRssWeights <- function( lambda2 = 0, selection = c("ldQuadratic", "minFbeta"), maxSwaps = 100, - ... + methodArgs = list() ) { penalty <- arg_match(penalty) selection <- arg_match(selection) @@ -2092,7 +2153,7 @@ l0learnRssWeights <- function( lambda0 = lambda0, lambda2 = lambda2, maxSwaps = maxSwaps, - dotArgs = list(...) + dotArgs = methodArgs ), selection ) @@ -2111,27 +2172,29 @@ l0learnRssWeights <- function( #' @param y A numeric response vector. #' @param penalty Either "SCAD" or "MCP". #' @param nfolds Number of cross-validation folds. Default is 5. -#' @param ... Additional arguments passed through to `ncvreg::cv.ncvreg`. +#' @param methodArgs Optional named list of options passed through to +#' `ncvreg::cv.ncvreg`. #' @return A numeric vector of length `ncol(X)` of variant weights. #' @importFrom stats coef #' @keywords internal -ncvregWeights <- function(X, y, penalty, nfolds = 5, ...) { +#' @importFrom checkmate assertCount +ncvregWeights <- function(X, y, penalty, nfolds = 5, methodArgs = list()) { + assertCount(nfolds, positive = TRUE) if (!requireNamespace("ncvreg", quietly = TRUE)) { - msg <- glue( - "To use this function, please install ncvreg: ", - "https://cran.r-project.org/package=ncvreg" - ) - abort(msg) + abort("Package 'ncvreg' is required for this function.") } eff.wgt <- matrix(0, ncol = 1, nrow = ncol(X)) keep <- .dropZeroVariance(X, "ncvregWeights") - fit <- ncvreg::cv.ncvreg( - X = X[, keep, drop = FALSE], - y = y, - penalty = penalty, - nfolds = nfolds, - ... + callArgs <- list_modify( + list( + X = X[, keep, drop = FALSE], + y = y, + penalty = penalty, + nfolds = nfolds + ), + !!!methodArgs ) + fit <- exec(ncvreg::cv.ncvreg, !!!callArgs) eff.wgt[keep] <- coef(fit, lambda = fit$lambda.min)[-1] return(eff.wgt) } @@ -2144,7 +2207,8 @@ ncvregWeights <- function(X, y, penalty, nfolds = 5, ...) { #' @param X A numeric matrix of predictors. #' @param y A numeric response vector. #' @param nfolds Number of cross-validation folds. Default is 5. -#' @param ... Additional arguments passed through to `ncvreg::cv.ncvreg`. +#' @param methodArgs Optional named list of options passed through to +#' `ncvreg::cv.ncvreg`. #' @return A numeric vector of length `ncol(X)` of variant weights. #' @examples #' data(eqtlRegionExample) @@ -2152,8 +2216,14 @@ ncvregWeights <- function(X, y, penalty, nfolds = 5, ...) { #' y <- eqtlRegionExample$yRes #' scadWeights(X, y) #' @export -scadWeights <- function(X, y, nfolds = 5, ...) { - ncvregWeights(X, y, penalty = "SCAD", nfolds = nfolds, ...) +scadWeights <- function(X, y, nfolds = 5, methodArgs = list()) { + ncvregWeights( + X, + y, + penalty = "SCAD", + nfolds = nfolds, + methodArgs = methodArgs + ) } #' Compute Weights Using MCP-Penalized Regression @@ -2164,7 +2234,8 @@ scadWeights <- function(X, y, nfolds = 5, ...) { #' @param X A numeric matrix of predictors. #' @param y A numeric response vector. #' @param nfolds Number of cross-validation folds. Default is 5. -#' @param ... Additional arguments passed through to `ncvreg::cv.ncvreg`. +#' @param methodArgs Optional named list of options passed through to +#' `ncvreg::cv.ncvreg`. #' @return A numeric vector of length `ncol(X)` of variant weights. #' @examples #' data(eqtlRegionExample) @@ -2172,8 +2243,14 @@ scadWeights <- function(X, y, nfolds = 5, ...) { #' y <- eqtlRegionExample$yRes #' mcpWeights(X, y) #' @export -mcpWeights <- function(X, y, nfolds = 5, ...) { - ncvregWeights(X, y, penalty = "MCP", nfolds = nfolds, ...) +mcpWeights <- function(X, y, nfolds = 5, methodArgs = list()) { + ncvregWeights( + X, + y, + penalty = "MCP", + nfolds = nfolds, + methodArgs = methodArgs + ) } #' Compute Weights Using L0Learn @@ -2189,7 +2266,8 @@ mcpWeights <- function(X, y, nfolds = 5, ...) { #' @param penalty Type of regularization: "L0", "L0L1", or "L0L2". Default is #' "L0". #' @param nFolds Number of cross-validation folds. Default is 5. -#' @param ... Additional arguments passed through to `L0Learn::L0Learn.cvfit` +#' @param methodArgs Optional named list of options passed through to +#' `L0Learn::L0Learn.cvfit` #' (e.g. `nGamma`, `gammaMin`, `gammaMax`, `algorithm`, `maxSuppSize`). #' @return A numeric vector of length `ncol(X)` of variant weights. #' @examples @@ -2198,23 +2276,28 @@ mcpWeights <- function(X, y, nfolds = 5, ...) { #' y <- eqtlRegionExample$yRes #' l0learnWeights(X, y) #' @export -l0learnWeights <- function(X, y, penalty = "L0", nFolds = 5, ...) { +l0learnWeights <- function( + X, + y, + penalty = "L0", + nFolds = 5, + methodArgs = list() +) { if (!requireNamespace("L0Learn", quietly = TRUE)) { - msg <- glue( - "To use this function, please install L0Learn: ", - "https://cran.r-project.org/package=L0Learn" - ) - abort(msg) + abort("Package 'L0Learn' is required for this function.") } eff.wgt <- matrix(0, ncol = 1, nrow = ncol(X)) keep <- .dropZeroVariance(X, "l0learnWeights") - fit <- L0Learn::L0Learn.cvfit( - x = X[, keep, drop = FALSE], - y = y, - penalty = penalty, - nFolds = nFolds, - ... + callArgs <- list_modify( + list( + x = X[, keep, drop = FALSE], + y = y, + penalty = penalty, + nFolds = nFolds + ), + !!!methodArgs ) + fit <- exec(L0Learn::L0Learn.cvfit, !!!callArgs) # Find (gamma, lambda) minimizing CV error across the entire path. cvMins <- map_dbl(fit$cvMeans, .rssMinNumeric) gammaIdx <- which.min(cvMins) @@ -2246,7 +2329,8 @@ l0learnWeights <- function(X, y, penalty = "L0", nFolds = 5, ...) { #' @param thin Thinning interval. #' @param etaArgs Optional named list of additional arguments included in the #' `ETA` linear-term specification (e.g. `list(probIn = 0.05)` for BayesB). -#' @param ... Additional arguments passed through to `BGLR::BGLR`. +#' @param methodArgs Optional named list of options passed through to +#' `BGLR::BGLR`. #' @return A numeric vector of length `ncol(X)` of variant weights. #' @keywords internal bglrWeights <- function( @@ -2257,14 +2341,10 @@ bglrWeights <- function( burnIn, thin, etaArgs = list(), - ... + methodArgs = list() ) { if (!requireNamespace("BGLR", quietly = TRUE)) { - msg <- glue( - "To use this function, please install BGLR: ", - "https://cran.r-project.org/package=BGLR" - ) - abort(msg) + abort("Package 'BGLR' is required for this function.") } eff.wgt <- rep(0, ncol(X)) keep <- .dropZeroVariance(X, "bglrWeights") @@ -2275,16 +2355,19 @@ bglrWeights <- function( saveAt <- str_c(tmpdir, .Platform$file.sep) eta <- list(c(list(X = X[, keep, drop = FALSE], model = model), etaArgs)) - fit <- BGLR::BGLR( - y = y, - ETA = eta, - nIter = nIter, - burnIn = burnIn, - thin = thin, - saveAt = saveAt, - verbose = FALSE, - ... + callArgs <- list_modify( + list( + y = y, + ETA = eta, + nIter = nIter, + burnIn = burnIn, + thin = thin, + saveAt = saveAt, + verbose = FALSE + ), + !!!methodArgs ) + fit <- exec(BGLR::BGLR, !!!callArgs) eff.wgt[keep] <- as.numeric(fit$ETA[[1]]$b) return(eff.wgt) } @@ -2307,13 +2390,15 @@ bglrWeights <- function( #' @param burnIn Number of burn-in iterations. Default is 2000. #' @param thin Thinning interval. Default is 5. #' @param probIn Prior inclusion probability for each marker. Default is 0.2. -#' @param ... Additional arguments passed through to `BGLR::BGLR`. +#' @param methodArgs Optional named list of options passed through to +#' `BGLR::BGLR`. #' @return A numeric vector of length `ncol(X)` of variant weights. #' @examples #' data(eqtlRegionExample) #' X <- eqtlRegionExample$X[, 1:30] #' y <- eqtlRegionExample$yRes #' bayesBWeights(X, y) +#' @importFrom checkmate assertCount assertNumber #' @export bayesBWeights <- function( X, @@ -2322,8 +2407,12 @@ bayesBWeights <- function( burnIn = 2000, thin = 5, probIn = 0.2, - ... + methodArgs = list() ) { + assertCount(nIter, positive = TRUE) + assertCount(burnIn) + assertCount(thin, positive = TRUE) + assertNumber(probIn, lower = 0, upper = 1) bglrWeights( X, y, @@ -2332,7 +2421,7 @@ bayesBWeights <- function( burnIn = burnIn, thin = thin, etaArgs = list(probIn = probIn), - ... + methodArgs = methodArgs ) } @@ -2353,15 +2442,27 @@ bayesBWeights <- function( #' @param nIter Number of MCMC iterations. Default is 10000. #' @param burnIn Number of burn-in iterations. Default is 2000. #' @param thin Thinning interval. Default is 5. -#' @param ... Additional arguments passed through to `BGLR::BGLR`. +#' @param methodArgs Optional named list of options passed through to +#' `BGLR::BGLR`. #' @return A numeric vector of length `ncol(X)` of variant weights. #' @examples #' data(eqtlRegionExample) #' X <- eqtlRegionExample$X[, 1:30] #' y <- eqtlRegionExample$yRes #' bLassoWeights(X, y) +#' @importFrom checkmate assertCount #' @export -bLassoWeights <- function(X, y, nIter = 10000, burnIn = 2000, thin = 5, ...) { +bLassoWeights <- function( + X, + y, + nIter = 10000, + burnIn = 2000, + thin = 5, + methodArgs = list() +) { + assertCount(nIter, positive = TRUE) + assertCount(burnIn) + assertCount(thin, positive = TRUE) bglrWeights( X, y, @@ -2369,7 +2470,7 @@ bLassoWeights <- function(X, y, nIter = 10000, burnIn = 2000, thin = 5, ...) { nIter = nIter, burnIn = burnIn, thin = thin, - ... + methodArgs = methodArgs ) } @@ -2390,7 +2491,8 @@ bLassoWeights <- function(X, y, nIter = 10000, burnIn = 2000, thin = 5, ...) { #' @param y A numeric response vector. #' @param fittingMethod One of "VB", "Gibbs", or "Adaptive_Gibbs". Default is #' "VB". -#' @param ... Additional arguments passed through to `RcppDPR::fit_model`. +#' @param methodArgs Optional named list of options passed through to +#' `RcppDPR::fit_model`. #' @param retainFit Logical. Attach the full fitted-model object to the result. #' Default \code{FALSE}. #' @param nK Integer. Number of variational mixture components for the VB fit @@ -2404,25 +2506,30 @@ bLassoWeights <- function(X, y, nIter = 10000, burnIn = 2000, thin = 5, ...) { #' y <- eqtlRegionExample$yRes #' dprWeights(X, y) #' @export -dprWeights <- function(X, y, fittingMethod = "VB", retainFit = FALSE, ...) { +dprWeights <- function( + X, + y, + fittingMethod = "VB", + retainFit = FALSE, + methodArgs = list() +) { if (!requireNamespace("RcppDPR", quietly = TRUE)) { - msg <- glue( - "To use this function, please install RcppDPR: ", - "https://cran.r-project.org/package=RcppDPR" - ) - abort(msg) + abort("Package 'RcppDPR' is required for this function.") } eff.wgt <- rep(0, ncol(X)) keep <- .dropZeroVariance(X, "dprWeights") w <- matrix(1, nrow = nrow(X), ncol = 1) - fit <- RcppDPR::fit_model( - y = y, - w = w, - x = X[, keep, drop = FALSE], - rotate_variables = FALSE, - fitting_method = fittingMethod, - ... + callArgs <- list_modify( + list( + y = y, + w = w, + x = X[, keep, drop = FALSE], + rotate_variables = FALSE, + fitting_method = fittingMethod + ), + !!!methodArgs ) + fit <- exec(RcppDPR::fit_model, !!!callArgs) eff.wgt[keep] <- as.numeric(fit$beta + fit$alpha) if (retainFit) { attr(eff.wgt, "fit") <- fit @@ -2437,8 +2544,14 @@ dprWeights <- function(X, y, fittingMethod = "VB", retainFit = FALSE, ...) { #' y <- eqtlRegionExample$yRes #' dprVbWeights(X, y) #' @export -dprVbWeights <- function(X, y, nK = 8, retainFit = FALSE, ...) { - dprWeights(X, y, fittingMethod = "VB", n_k = nK, retainFit = retainFit, ...) +dprVbWeights <- function(X, y, nK = 8, retainFit = FALSE, methodArgs = list()) { + dprWeights( + X, + y, + fittingMethod = "VB", + retainFit = retainFit, + methodArgs = c(list(n_k = nK), methodArgs) + ) } #' @rdname dprWeights @@ -2450,15 +2563,23 @@ dprVbWeights <- function(X, y, nK = 8, retainFit = FALSE, ...) { #' colnames(X) <- sprintf("chr1:%d:A:G", 100L * (1:p)) #' y <- X[, 1] * 0.5 + rnorm(n) #' dprGibbsWeights(X, y, sStep = 500) +#' @importFrom checkmate assertCount assertFlag #' @export -dprGibbsWeights <- function(X, y, sStep = 5000, retainFit = FALSE, ...) { +dprGibbsWeights <- function( + X, + y, + sStep = 5000, + retainFit = FALSE, + methodArgs = list() +) { + assertCount(sStep, positive = TRUE) + assertFlag(retainFit) dprWeights( X, y, fittingMethod = "Gibbs", - s_step = sStep, retainFit = retainFit, - ... + methodArgs = c(list(s_step = sStep), methodArgs) ) } @@ -2471,14 +2592,21 @@ dprGibbsWeights <- function(X, y, sStep = 5000, retainFit = FALSE, ...) { #' colnames(X) <- sprintf("chr1:%d:A:G", 100L * (1:p)) #' y <- X[, 1] * 0.5 + rnorm(n) #' dprAdaptiveGibbsWeights(X, y) +#' @importFrom checkmate assertFlag #' @export -dprAdaptiveGibbsWeights <- function(X, y, retainFit = FALSE, ...) { +dprAdaptiveGibbsWeights <- function( + X, + y, + retainFit = FALSE, + methodArgs = list() +) { + assertFlag(retainFit) dprWeights( X, y, fittingMethod = "Adaptive_Gibbs", retainFit = retainFit, - ... + methodArgs = methodArgs ) } #' @title Mr.Mash Wrapper @@ -2515,7 +2643,6 @@ dprAdaptiveGibbsWeights <- function(X, y, retainFit = FALSE, ...) { #' @param tol The tolerance for convergence. Default is 0.01. #' @param verbose A logical indicating whether to print verbose output. Default #' is FALSE. -#' @param ... Additional arguments to be passed to mr.mash. #' #' @param V Optional residual covariance matrix (conditions x conditions), or #' \code{NULL} to estimate it. @@ -2576,29 +2703,55 @@ mrmashWrapper <- function( bInitMethod = "enet", maxIter = 5000, tol = 0.01, - verbose = FALSE, - ... + verbose = FALSE ) { .mrmashRequirePackages() - p <- as.list(environment()) - .mrmashValidateWrapper(p) - p$bInitMethod <- .mrmashResolveBInit(Y, bInitMethod) + .mrmashValidateWrapper( + X = X, + Y = Y, + priorGrid = priorGrid, + dataDrivenPriorMatrices = dataDrivenPriorMatrices, + canonicalPriorMatrices = canonicalPriorMatrices + ) + bInitMethod <- .mrmashResolveBInit(Y, bInitMethod) if (is.null(sumstats)) { - p$sumstats <- .mrmashComputeSumstats(p) + sumstats <- .mrmashComputeSumstats(X, Y, standardize, nthreads) } # Shared prior-covariance builder (also used by mrmashRssWeights). priorBuilt <- buildMrmashPriorMatrices( - Bhat = p$sumstats$Bhat, - Shat = p$sumstats$Shat, + Bhat = sumstats$Bhat, + Shat = sumstats$Shat, K = ncol(Y), dataDrivenPriorMatrices = dataDrivenPriorMatrices, canonicalPriorMatrices = canonicalPriorMatrices, priorGrid = priorGrid ) time1 <- proc.time() - bInit <- as.matrix(.mrmashInitCoefficients(p)$Bhat) - vInit <- .mrmashInitV(p) - fitMrmash <- .mrmashFit(p, priorBuilt$S0, bInit, vInit) + bInit <- as.matrix( + .mrmashInitCoefficients( + X, + Y, + bInitMethod, + standardize, + nthreads + )$Bhat + ) + vInit <- .mrmashInitV(X, Y, V, updateV, updateVMethod) + fitMrmash <- .mrmashFit( + priorBuilt$S0, + bInit, + vInit, + X = X, + Y = Y, + updateW0 = updateW0, + tol = tol, + maxIter = maxIter, + standardize = standardize, + verbose = verbose, + updateVMethod = updateVMethod, + w0Threshold = w0Threshold, + nthreads = nthreads + ) fitMrmash$analysis_time <- proc.time()["elapsed"] - time1["elapsed"] fitMrmash } @@ -2607,41 +2760,36 @@ mrmashWrapper <- function( # @noRd .mrmashRequirePackages <- function() { if (!requireNamespace("glmnet", quietly = TRUE)) { - msg <- glue( - "To use this function, please install glmnet: ", - "https://cran.r-project.org/web/packages/glmnet/index.html" - ) - abort(msg) + abort("Package 'glmnet' is required for this function.") } if (!requireNamespace("mr.mashr", quietly = TRUE)) { - msg <- glue( - "To use this function, please install mr.mashr: ", - "https://github.com/stephenslab/mr.mashr" - ) - abort(msg) + abort("Package 'mr.mashr' is required for this function.") } } # Input validation for the individual-level mr.mash wrapper. # @noRd -.mrmashValidateWrapper <- function(p) { +#' @importFrom checkmate assertMatrix +.mrmashValidateWrapper <- function( + X, + Y, + priorGrid, + dataDrivenPriorMatrices, + canonicalPriorMatrices +) { if (!exists(".Random.seed")) { inform( "! No seed has been set. Please set seed for reproducable result. " ) } - if (!is.matrix(p$X) || !is.matrix(p$Y)) { + if (!is.matrix(X) || !is.matrix(Y)) { abort("X and Y must be matrices.") } - if (nrow(p$X) != nrow(p$Y)) { - abort("X and Y must have the same number of rows.") - } - if (!is.null(p$priorGrid) && !is.vector(p$priorGrid)) { + assertMatrix(Y, nrows = nrow(X)) + if (!is.null(priorGrid) && !is.vector(priorGrid)) { abort("priorGrid must be a vector.") } - if ( - is.null(p$dataDrivenPriorMatrices) && !isTRUE(p$canonicalPriorMatrices) - ) { + if (is.null(dataDrivenPriorMatrices) && !isTRUE(canonicalPriorMatrices)) { msg <- glue( "Please provide dataDrivenPriorMatrices or set ", "canonicalPriorMatrices = TRUE." @@ -2667,34 +2815,34 @@ mrmashWrapper <- function( # Univariate summary statistics (Bhat/Shat) for the prior + init. # @noRd -.mrmashComputeSumstats <- function(p) { +.mrmashComputeSumstats <- function(X, Y, standardize, nthreads) { mr.mashr::compute_univariate_sumstats( - p$X, - p$Y, - standardize = p$standardize, + X, + Y, + standardize = standardize, standardize.response = FALSE, - mc.cores = p$nthreads + mc.cores = nthreads ) } # Initial coefficient matrix via graphical-lasso or univariate glmnet. # @noRd -.mrmashInitCoefficients <- function(p) { - if (p$bInitMethod == "glasso") { +.mrmashInitCoefficients <- function(X, Y, bInitMethod, standardize, nthreads) { + if (bInitMethod == "glasso") { return(computeCoefficientsGlasso( - p$X, - p$Y, - standardize = p$standardize, - nthreads = p$nthreads, + X, + Y, + standardize = standardize, + nthreads = nthreads, Xnew = NULL )) } computeCoefficientsUnivGlmnet( - p$X, - p$Y, + X, + Y, alpha = 0.5, - standardize = p$standardize, - nthreads = p$nthreads, + standardize = standardize, + nthreads = nthreads, Xnew = NULL ) } @@ -2702,18 +2850,18 @@ mrmashWrapper <- function( # Robust residual-covariance init. Returns list(V, updateV); a rank-deficient V # is ridge-regularized and its update disabled. # @noRd -.mrmashInitV <- function(p) { - if (!is.null(p$V)) { - return(list(V = p$V, updateV = p$updateV)) +.mrmashInitV <- function(X, Y, V, updateV, updateVMethod) { + if (!is.null(V)) { + return(list(V = V, updateV = updateV)) } - V <- .mrmashComputeVInit(p$X, p$Y, any(is.na(p$Y))) - if (p$updateVMethod == "diagonal") { - return(list(V = diag(diag(V)), updateV = p$updateV)) + V <- .mrmashComputeVInit(X, Y, any(is.na(Y))) + if (updateVMethod == "diagonal") { + return(list(V = diag(diag(V)), updateV = updateV)) } if (any(eigen(V)$values < 1e-8)) { return(list(V = V + diag(1e-8, nrow(V)), updateV = FALSE)) } - list(V = V, updateV = p$updateV) + list(V = V, updateV = updateV) } # Compute V_init via mr.mashr (cov for complete Y, flash when Y has missing). @@ -2740,24 +2888,38 @@ mrmashWrapper <- function( # Run mr.mash with the resolved prior / init / V. # @noRd -.mrmashFit <- function(p, S0, bInit, vInit) { +.mrmashFit <- function( + S0, + bInit, + vInit, + X, + Y, + updateW0, + tol, + maxIter, + standardize, + verbose, + updateVMethod, + w0Threshold, + nthreads +) { mr.mashr::mr.mash( - X = p$X, - Y = p$Y, + X = X, + Y = Y, V = vInit$V, S0 = S0, w0 = computeW0(bInit, length(S0)), - update_w0 = p$updateW0, - tol = p$tol, - max_iter = p$maxIter, + update_w0 = updateW0, + tol = tol, + max_iter = maxIter, convergence_criterion = "ELBO", compute_ELBO = TRUE, - standardize = p$standardize, - verbose = p$verbose, + standardize = standardize, + verbose = verbose, update_V = vInit$updateV, - update_V_method = p$updateVMethod, - w0_threshold = p$w0Threshold, - nthreads = p$nthreads, + update_V_method = updateVMethod, + w0_threshold = w0Threshold, + nthreads = nthreads, mu1_init = bInit ) } @@ -2782,6 +2944,7 @@ mrmashWrapper <- function( #' Y <- matrix(rnorm(nrow(X) * 3), nrow(X), 3) #' computeCoefficientsGlasso(X = X, Y = Y, standardize = TRUE, #' nthreads = 1L, Xnew = NULL) +#' @importFrom checkmate assertFlag assertInt #' @export computeCoefficientsGlasso <- function( X, @@ -2790,6 +2953,8 @@ computeCoefficientsGlasso <- function( nthreads, Xnew = NULL ) { + assertFlag(standardize) + assertInt(nthreads) n <- nrow(X) p <- ncol(X) r <- ncol(Y) @@ -2926,11 +3091,8 @@ rescaleCovW0 <- function(w0) { groups <- str_remove(names(w0), "_[^_]+$") groupList <- split(w0, groups) - # get per group sum - groupWeight <- map(groupList, sum) - - # Renormalize values within each group - weightsList <- unlist(groupWeight) + # get per group sum -- one scalar per group + weightsList <- map_dbl(groupList, sum) sumWeights <- sum(weightsList) if (sumWeights > 0) { weightsList <- weightsList / sumWeights diff --git a/R/relatednessQc.R b/R/relatednessQc.R index 8246e8961..98eb8dd09 100644 --- a/R/relatednessQc.R +++ b/R/relatednessQc.R @@ -67,40 +67,94 @@ filterRelatedness <- function( ) { .relatednessRequirePackages() analysisType <- arg_match(analysisType) - p <- as.list(environment()) - p$relatedness <- as_tibble(relatedness) + relatedness <- as_tibble(relatedness) if (analysisType == "maximizeCases" && is.null(phenoData)) { abort("Must provide phenoData when analysisType is 'maximizeCases'") } # Phase 1: graph-based pre-pruning of large components. - highRelatedIndiv <- .relatednessPrune(p) - kin <- .relatednessRemovePruned(p$relatedness, highRelatedIndiv, p) + highRelatedIndiv <- .relatednessPrune( + relatedness, + relatednessValue, + relatednessThreshold, + relatednessIid1, + relatednessIid2, + maxComponentSize, + reduceFraction, + verbose + ) + kin <- .relatednessRemovePruned( + relatedness, + highRelatedIndiv, + relatednessIid1, + relatednessIid2 + ) # Phase 2: plinkQC-based filtering (analysis-type dependent). - plinkqcArgs <- .relatednessBuildPlinkqcArgs(p) - filtered <- .relatednessPhase2(kin, plinkqcArgs, analysisType, p) + plinkqcArgs <- .relatednessBuildPlinkqcArgs( + otherCriterion, + relatednessThreshold, + relatednessIid1, + relatednessIid2, + otherCriterionThreshold, + otherCriterionDirection, + relatednessFid1, + relatednessFid2, + relatednessValue, + otherCriterionIid, + otherCriterionMeasure, + verbose + ) + filtered <- .relatednessPhase2( + kin, + plinkqcArgs, + analysisType, + phenoData, + phenoCol, + relatednessIid1, + relatednessIid2 + ) # Phase 3: iterative cleanup + combine with the graph-pruned individuals. allExclude <- .relatednessIterativeCleanup( filtered$kin, filtered$allExclude, plinkqcArgs, - p + maxIterations, + verbose, + relatednessIid1, + relatednessIid2, + relatednessValue, + relatednessThreshold ) allExclude <- unique(c(allExclude, highRelatedIndiv)) - .relatednessReport(allExclude, p) + .relatednessReport(allExclude, verbose, relatednessThreshold) allExclude } # Phase-2 dispatch: maximizeUnrelated runs plinkQC directly; maximizeCases # preserves cases. Returns list(allExclude, kin). # @noRd -.relatednessPhase2 <- function(kin, plinkqcArgs, analysisType, p) { +.relatednessPhase2 <- function( + kin, + plinkqcArgs, + analysisType, + phenoData, + phenoCol, + relatednessIid1, + relatednessIid2 +) { if (analysisType == "maximizeUnrelated") { return(list( allExclude = .relatednessRunPlinkqc(kin, plinkqcArgs)$IID, kin = kin )) } - .relatednessMaximizeCases(kin, plinkqcArgs, p) + .relatednessMaximizeCases( + kin, + plinkqcArgs, + phenoData, + phenoCol, + relatednessIid1, + relatednessIid2 + ) } # @noRd @@ -113,17 +167,34 @@ filterRelatedness <- function( } } +# Size of the largest component, or 0 when the graph has none. Guards +# max(integer(0)), which warns and returns -Inf -- with no related pairs the +# loop below must simply not run. +# @noRd +.relatednessLargestComponent <- function(workingComp) { + if (length(workingComp$csize) == 0L) 0L else max(workingComp$csize) +} + # Graph pre-pruning: iteratively remove the highest-degree nodes of any # component larger than maxComponentSize. Returns the pruned individuals. # @noRd -.relatednessPrune <- function(p) { +.relatednessPrune <- function( + relatedness, + relatednessValue, + relatednessThreshold, + relatednessIid1, + relatednessIid2, + maxComponentSize, + reduceFraction, + verbose +) { relatedPairs <- filter( - p$relatedness, - .data[[p$relatednessValue]] >= p$relatednessThreshold + relatedness, + .data[[relatednessValue]] >= relatednessThreshold ) edges <- select( relatedPairs, - all_of(c(p$relatednessIid1, p$relatednessIid2)) + all_of(c(relatednessIid1, relatednessIid2)) ) # igraph requires a base data.frame (it sets row names on the input). workingGraph <- igraph::graph_from_data_frame( @@ -132,9 +203,14 @@ filterRelatedness <- function( ) workingComp <- igraph::components(workingGraph) highRelatedIndiv <- character(0) - while (max(workingComp$csize) > p$maxComponentSize) { - .relatednessPruneMessage(workingComp, p) - nodesToRemove <- .relatednessNodesToRemove(workingGraph, workingComp, p) + while (.relatednessLargestComponent(workingComp) > maxComponentSize) { + .relatednessPruneMessage(workingComp, verbose, reduceFraction) + nodesToRemove <- .relatednessNodesToRemove( + workingGraph, + workingComp, + maxComponentSize, + reduceFraction + ) highRelatedIndiv <- c(highRelatedIndiv, nodesToRemove) workingGraph <- igraph::delete_vertices(workingGraph, nodesToRemove) workingComp <- igraph::components(workingGraph) @@ -143,11 +219,11 @@ filterRelatedness <- function( } # @noRd -.relatednessPruneMessage <- function(workingComp, p) { - if (p$verbose) { +.relatednessPruneMessage <- function(workingComp, verbose, reduceFraction) { + if (verbose) { msg <- glue( "Largest component has {max(workingComp$csize)} individuals. ", - "Removing top {round(p$reduceFraction * 100)}% ", + "Removing top {round(reduceFraction * 100)}% ", "highest-degree nodes." ) inform(msg) @@ -157,14 +233,19 @@ filterRelatedness <- function( # The highest-degree nodes to remove across all over-sized components. # @noRd -.relatednessNodesToRemove <- function(workingGraph, workingComp, p) { - largeCompIds <- which(workingComp$csize > p$maxComponentSize) - unlist(map( +.relatednessNodesToRemove <- function( + workingGraph, + workingComp, + maxComponentSize, + reduceFraction +) { + largeCompIds <- which(workingComp$csize > maxComponentSize) + list_c(map( largeCompIds, .relatednessCompNodesToRemove, workingGraph = workingGraph, membership = workingComp$membership, - reduceFraction = p$reduceFraction + reduceFraction = reduceFraction )) } @@ -183,59 +264,84 @@ filterRelatedness <- function( # Drop the pre-pruned individuals from the relatedness data. # @noRd -.relatednessRemovePruned <- function(relatedness, highRelatedIndiv, p) { +.relatednessRemovePruned <- function( + relatedness, + highRelatedIndiv, + relatednessIid1, + relatednessIid2 +) { filter( relatedness, - !is_in(.data[[p$relatednessIid1]], highRelatedIndiv) & - !is_in(.data[[p$relatednessIid2]], highRelatedIndiv) + !is_in(.data[[relatednessIid1]], highRelatedIndiv) & + !is_in(.data[[relatednessIid2]], highRelatedIndiv) ) } # @noRd -.relatednessBuildPlinkqcArgs <- function(p) { +.relatednessBuildPlinkqcArgs <- function( + otherCriterion, + relatednessThreshold, + relatednessIid1, + relatednessIid2, + otherCriterionThreshold, + otherCriterionDirection, + relatednessFid1, + relatednessFid2, + relatednessValue, + otherCriterionIid, + otherCriterionMeasure, + verbose +) { list( - otherCriterion = p$otherCriterion, - relatednessTh = p$relatednessThreshold, - relatednessIID1 = p$relatednessIid1, - relatednessIID2 = p$relatednessIid2, - otherCriterionTh = p$otherCriterionThreshold, - otherCriterionThDirection = p$otherCriterionDirection, - relatednessFID1 = p$relatednessFid1, - relatednessFID2 = p$relatednessFid2, - relatednessRelatedness = p$relatednessValue, - otherCriterionIID = p$otherCriterionIid, - otherCriterionMeasure = p$otherCriterionMeasure, - verbose = p$verbose + otherCriterion = otherCriterion, + relatednessTh = relatednessThreshold, + relatednessIID1 = relatednessIid1, + relatednessIID2 = relatednessIid2, + otherCriterionTh = otherCriterionThreshold, + otherCriterionThDirection = otherCriterionDirection, + relatednessFID1 = relatednessFid1, + relatednessFID2 = relatednessFid2, + relatednessRelatedness = relatednessValue, + otherCriterionIID = otherCriterionIid, + otherCriterionMeasure = otherCriterionMeasure, + verbose = verbose ) } # maximizeCases: preserve cases, preferentially remove controls. Returns # list(allExclude, kin) (kin is restricted to phenotyped individuals). # @noRd -.relatednessMaximizeCases <- function(kin, plinkqcArgs, p) { - phenoData <- as_tibble(p$phenoData) - phenoData <- filter(phenoData, !is.na(.data[[p$phenoCol]])) +.relatednessMaximizeCases <- function( + kin, + plinkqcArgs, + phenoData, + phenoCol, + relatednessIid1, + relatednessIid2 +) { + phenoData <- as_tibble(phenoData) + phenoData <- filter(phenoData, !is.na(.data[[phenoCol]])) relatedIndividuals <- unique(c( - kin[[p$relatednessIid1]], - kin[[p$relatednessIid2]] + kin[[relatednessIid1]], + kin[[relatednessIid2]] )) phenoData <- filter(phenoData, is_in(.data$IID, relatedIndividuals)) relatedCases <- phenoData |> - filter(.data[[p$phenoCol]] == 1) |> + filter(.data[[phenoCol]] == 1) |> pull("IID") relatedControls <- phenoData |> - filter(.data[[p$phenoCol]] == 0) |> + filter(.data[[phenoCol]] == 0) |> pull("IID") kin <- filter( kin, - is_in(.data[[p$relatednessIid1]], phenoData$IID) & - is_in(.data[[p$relatednessIid2]], phenoData$IID) + is_in(.data[[relatednessIid1]], phenoData$IID) & + is_in(.data[[relatednessIid2]], phenoData$IID) ) # Step 1: filter among cases. caseKin <- filter( kin, - is_in(.data[[p$relatednessIid1]], relatedCases) & - is_in(.data[[p$relatednessIid2]], relatedCases) + is_in(.data[[relatednessIid1]], relatedCases) & + is_in(.data[[relatednessIid2]], relatedCases) ) relCases <- .relatednessRunPlinkqc(caseKin, plinkqcArgs) casesKeep <- setdiff(relatedCases, relCases$IID) @@ -244,14 +350,15 @@ filterRelatedness <- function( kin, casesKeep, relatedControls, - p + relatednessIid1, + relatednessIid2 ) # Step 3: filter among the remaining controls. controlsKeep <- setdiff(relatedControls, controlsExclude) controlKin <- filter( kin, - is_in(.data[[p$relatednessIid1]], controlsKeep) & - is_in(.data[[p$relatednessIid2]], controlsKeep) + is_in(.data[[relatednessIid1]], controlsKeep) & + is_in(.data[[relatednessIid2]], controlsKeep) ) relControls <- .relatednessRunPlinkqc(controlKin, plinkqcArgs) list( @@ -263,9 +370,15 @@ filterRelatedness <- function( # Controls related to a retained case (row order preserved; a case--control # edge excludes the control, mirroring the original per-row if / else-if). # @noRd -.relatednessControlsToExclude <- function(kin, casesKeep, relatedControls, p) { - iid1 <- kin[[p$relatednessIid1]] - iid2 <- kin[[p$relatednessIid2]] +.relatednessControlsToExclude <- function( + kin, + casesKeep, + relatedControls, + relatednessIid1, + relatednessIid2 +) { + iid1 <- kin[[relatednessIid1]] + iid2 <- kin[[relatednessIid2]] mask1 <- is_in(iid1, casesKeep) & is_in(iid2, relatedControls) mask2 <- is_in(iid2, casesKeep) & is_in(iid1, relatedControls) contrib <- case_when( @@ -279,11 +392,27 @@ filterRelatedness <- function( # Iteratively re-run plinkQC on the still-related pairs until none remain or # maxIterations is hit. Returns the accumulated exclusion set. # @noRd -.relatednessIterativeCleanup <- function(kin, allExclude, plinkqcArgs, p) { - remaining <- .relatednessRemaining(kin, allExclude, p) +.relatednessIterativeCleanup <- function( + kin, + allExclude, + plinkqcArgs, + maxIterations, + verbose, + relatednessIid1, + relatednessIid2, + relatednessValue, + relatednessThreshold +) { + remainingArgs <- list( + relatednessIid1 = relatednessIid1, + relatednessIid2 = relatednessIid2, + relatednessValue = relatednessValue, + relatednessThreshold = relatednessThreshold + ) + remaining <- exec(.relatednessRemaining, kin, allExclude, !!!remainingArgs) iter <- 0L - while (nrow(remaining) > 0 && iter < p$maxIterations) { - if (p$verbose) { + while (nrow(remaining) > 0 && iter < maxIterations) { + if (verbose) { msg <- glue( "Iteration {iter + 1L}: {nrow(remaining)} related pairs ", "remaining." @@ -292,12 +421,17 @@ filterRelatedness <- function( } additional <- .relatednessRunPlinkqc(remaining, plinkqcArgs) allExclude <- c(allExclude, additional$IID) - remaining <- .relatednessRemaining(kin, allExclude, p) + remaining <- exec( + .relatednessRemaining, + kin, + allExclude, + !!!remainingArgs + ) iter <- iter + 1L } if (nrow(remaining) > 0) { msg <- glue( - "After {p$maxIterations} iterations, {nrow(remaining)} related ", + "After {maxIterations} iterations, {nrow(remaining)} related ", "pairs remain." ) warn(msg) @@ -307,21 +441,28 @@ filterRelatedness <- function( # The still-related pairs above threshold after excluding `allExclude`. # @noRd -.relatednessRemaining <- function(kin, allExclude, p) { +.relatednessRemaining <- function( + kin, + allExclude, + relatednessIid1, + relatednessIid2, + relatednessValue, + relatednessThreshold +) { remaining <- filter( kin, - !is_in(.data[[p$relatednessIid1]], allExclude) & - !is_in(.data[[p$relatednessIid2]], allExclude) + !is_in(.data[[relatednessIid1]], allExclude) & + !is_in(.data[[relatednessIid2]], allExclude) ) - filter(remaining, .data[[p$relatednessValue]] > p$relatednessThreshold) + filter(remaining, .data[[relatednessValue]] > relatednessThreshold) } # @noRd -.relatednessReport <- function(allExclude, p) { - if (p$verbose) { +.relatednessReport <- function(allExclude, verbose, relatednessThreshold) { + if (verbose) { msg <- glue( "{length(allExclude)} individuals excluded at kinship ", - "threshold {p$relatednessThreshold}" + "threshold {relatednessThreshold}" ) inform(msg) } diff --git a/R/sldscPostprocessingPipeline.R b/R/sldscPostprocessingPipeline.R index 1b6cf5730..763fd4876 100644 --- a/R/sldscPostprocessingPipeline.R +++ b/R/sldscPostprocessingPipeline.R @@ -334,9 +334,10 @@ sldscPostprocessingPipeline <- function( # Standardize the i-th single run (NULL + warning on failure). # @noRd +#' @importFrom rlang try_fetch .sldscStandardizeSingle <- function(i, trait, ctx) { catName <- ctx$targetCategories[i] - std <- tryCatch( + std <- try_fetch( standardizeSldscTrait( ctx$sldscData, trait, @@ -346,13 +347,11 @@ sldscPostprocessingPipeline <- function( MRef = ctx$MRef, targetCategories = catName ), - error = function(e) { - eMsg <- e$message + error = function(cnd) { msg <- glue( - "[sldsc] Failed to standardize single {catName} for ", - "{trait}: {eMsg}" + "[sldsc] Failed to standardize single {catName} for {trait}" ) - warn(msg) + warn(msg, parent = cnd) NULL } ) @@ -401,7 +400,7 @@ sldscPostprocessingPipeline <- function( if (is.null(getTraitRun(ctx$sldscData, trait, "joint"))) { return(empty) } - std <- tryCatch( + std <- try_fetch( standardizeSldscTrait( ctx$sldscData, trait, @@ -410,12 +409,9 @@ sldscPostprocessingPipeline <- function( MRef = ctx$MRef, targetCategories = ctx$targetCategories ), - error = function(e) { - eMsg <- e$message - msg <- glue( - "[sldsc] Failed to standardize joint for {trait}: {eMsg}" - ) - warn(msg) + error = function(cnd) { + msg <- glue("[sldsc] Failed to standardize joint for {trait}") + warn(msg, parent = cnd) NULL } ) diff --git a/R/sldscWrapper.R b/R/sldscWrapper.R index 9757b2f9a..2dc2f518d 100644 --- a/R/sldscWrapper.R +++ b/R/sldscWrapper.R @@ -29,6 +29,7 @@ } +#' @importFrom checkmate assertFileExists #' @title Read S-LDSC outputs from polyfun for one trait/run #' #' @description Reads the regression outputs produced by `polyfun/ldsc.py` for a @@ -56,12 +57,7 @@ #' @export readSldscTrait <- function(prefix) { files <- str_c(prefix, c(".results", ".log", ".part_delete")) - for (f in files) { - if (!file.exists(f)) { - msg <- glue("readSldscTrait: missing file: {f}") - abort(msg) - } - } + assertFileExists(files, access = "r", .var.name = "readSldscTrait input") results <- vroom(files[1], show_col_types = FALSE) cats <- as.character(results$Category) h2g <- .readSldscH2g(files[2]) @@ -128,6 +124,7 @@ readSldscTrait <- function(prefix) { } +#' @importFrom checkmate assertDirectoryExists #' @title Read target annotation files (.annot.gz) into one table #' #' @description Reads the per-chromosome polyfun `.annot.gz` files in a @@ -148,12 +145,11 @@ readSldscTrait <- function(prefix) { #' readSldscAnnot(sldsc) #' @export readSldscAnnot <- function(targetAnnoDir, annotCols = NULL) { - if (!dir.exists(targetAnnoDir)) { - msg <- glue( - "readSldscAnnot: targetAnnoDir does not exist: {targetAnnoDir}" - ) - abort(msg) - } + assertDirectoryExists( + targetAnnoDir, + access = "r", + .var.name = "targetAnnoDir" + ) annoFiles <- list.files( targetAnnoDir, pattern = "\\.annot\\.gz$", @@ -181,6 +177,7 @@ readSldscAnnot <- function(targetAnnoDir, annotCols = NULL) { } +#' @importFrom checkmate assertDirectoryExists #' @title Read PLINK allele-frequency files (.frq) into one table #' #' @description Reads the per-chromosome PLINK `.frq` files for the reference @@ -200,10 +197,7 @@ readSldscAnnot <- function(targetAnnoDir, annotCols = NULL) { #' head(readSldscFrq(sldsc, plinkName = "reference.")) #' @export readSldscFrq <- function(frqfileDir, plinkName = "ADSP_chr") { - if (!dir.exists(frqfileDir)) { - msg <- glue("readSldscFrq: frqfileDir does not exist: {frqfileDir}") - abort(msg) - } + assertDirectoryExists(frqfileDir, access = "r", .var.name = "frqfileDir") pat <- str_c( "^", str_replace_all(plinkName, "([.])", "\\\\\\1"), @@ -227,6 +221,7 @@ readSldscFrq <- function(frqfileDir, plinkName = "ADSP_chr") { } +#' @importFrom checkmate assertClass #' @title Compute per-annotation standard deviation, MAF-restricted #' #' @description Computes the standard deviation of each annotation column in the @@ -272,9 +267,7 @@ readSldscFrq <- function(frqfileDir, plinkName = "ADSP_chr") { #' @importFrom purrr map map_dbl compact reduce #' @export computeSldscAnnotSd <- function(sldscData, mafCutoff = 0.05, annotCols = NULL) { - if (!is(sldscData, "SldscData")) { - abort("computeSldscAnnotSd: `sldscData` must be an SldscData object.") - } + assertClass(sldscData, "SldscData") annot <- getAnnotData(sldscData) frq <- getFrqData(sldscData) if (mafCutoff > 0 && nrow(frq) == 0L) { @@ -347,6 +340,7 @@ computeSldscAnnotSd <- function(sldscData, mafCutoff = 0.05, annotCols = NULL) { } +#' @importFrom checkmate assertClass #' @title Reference-panel SNP count (the M_ref used to standardise tau*) #' #' @description `M_ref` is the number of SNPs in the REFERENCE PANEL over which @@ -398,9 +392,7 @@ computeSldscAnnotSd <- function(sldscData, mafCutoff = 0.05, annotCols = NULL) { #' computeSldscMRef(sldscData = sd) #' @export computeSldscMRef <- function(sldscData, mafCutoff = 0.05) { - if (!is(sldscData, "SldscData")) { - abort("computeSldscMRef: `sldscData` must be an SldscData object.") - } + assertClass(sldscData, "SldscData") frq <- getFrqData(sldscData) if (nrow(frq) > 0L) { return(as.integer( @@ -422,6 +414,7 @@ computeSldscMRef <- function(sldscData, mafCutoff = 0.05) { } +#' @importFrom checkmate assertClass #' @title Detect whether each annotation is binary or continuous #' #' @description Inspects each annotation column and returns whether its values @@ -461,9 +454,7 @@ computeSldscMRef <- function(sldscData, mafCutoff = 0.05) { #' isBinarySldscAnnot(sd) #' @export isBinarySldscAnnot <- function(sldscData, annotCols = NULL) { - if (!is(sldscData, "SldscData")) { - abort("isBinarySldscAnnot: `sldscData` must be an SldscData object.") - } + assertClass(sldscData, "SldscData") annot <- getAnnotData(sldscData) colsUse <- if (is.null(annotCols)) { getAnnotCols(sldscData) @@ -482,6 +473,7 @@ isBinarySldscAnnot <- function(sldscData, annotCols = NULL) { } +#' @importFrom checkmate assertClass #' @title Standardize tau and compute EnrichStat for one polyfun run #' #' @description Applies the Gazal standardization \eqn{\tau^*_C = \tau_C \cdot @@ -544,9 +536,7 @@ standardizeSldscTrait <- function( MRef, targetCategories = NULL ) { - if (!is(sldscData, "SldscData")) { - abort("standardizeSldscTrait: `sldscData` must be an SldscData object.") - } + assertClass(sldscData, "SldscData") mode <- arg_match(mode) traitData <- .stdTraitRun(sldscData, trait, mode, idx) targetCategories <- .stdTargetCategories( diff --git a/R/sumstatsQc.R b/R/sumstatsQc.R index 23aafadeb..11b1dd93a 100644 --- a/R/sumstatsQc.R +++ b/R/sumstatsQc.R @@ -69,7 +69,9 @@ NULL #' v2 <- data.frame(chrom = "1", pos = 2:4, alt = "A", ref = "G") #' mergeVariantInfo(v1, v2, all = TRUE) #' @export +#' @importFrom checkmate assertFlag mergeVariantInfo <- function(variants1, variants2, all = TRUE) { + assertFlag(all) df1 <- .variantsToDf(variants1) df2 <- .variantsToDf(variants2) @@ -177,20 +179,33 @@ resolveLdInput <- function( } # Run DENTIST on a single window, unpacking the shared tuning parameters. -.dentistCallSingle <- function(zScore, ldMat, nSample, p) { +.dentistCallSingle <- function( + zScore, + ldMat, + nSample, + pValueThreshold, + propSVD, + gcControl, + nIter, + gPvalueThreshold, + duprThreshold, + ncpus, + correctChenEtAlBug, + seed +) { dentistSingleWindow( zScore, R = ldMat, nSample = nSample, - pValueThreshold = p$pValueThreshold, - propSVD = p$propSVD, - gcControl = p$gcControl, - nIter = p$nIter, - gPvalueThreshold = p$gPvalueThreshold, - duprThreshold = p$duprThreshold, - ncpus = p$ncpus, - correctChenEtAlBug = p$correctChenEtAlBug, - seed = p$seed + pValueThreshold = pValueThreshold, + propSVD = propSVD, + gcControl = gcControl, + nIter = nIter, + gPvalueThreshold = gPvalueThreshold, + duprThreshold = duprThreshold, + ncpus = ncpus, + correctChenEtAlBug = correctChenEtAlBug, + seed = seed ) } @@ -202,7 +217,15 @@ resolveLdInput <- function( windowMode, windowSize, minDim, - p + pValueThreshold, + propSVD, + gcControl, + nIter, + gPvalueThreshold, + duprThreshold, + ncpus, + correctChenEtAlBug, + seed ) { if (windowMode == "distance") { windowDividedRes <- segmentByDist( @@ -225,7 +248,15 @@ resolveLdInput <- function( zScoreK, ldMatK, nSample, - p + pValueThreshold = pValueThreshold, + propSVD = propSVD, + gcControl = gcControl, + nIter = nIter, + gPvalueThreshold = gPvalueThreshold, + duprThreshold = duprThreshold, + ncpus = ncpus, + correctChenEtAlBug = correctChenEtAlBug, + seed = seed ) } mergeWindows(dentistResultByWindow, windowDividedRes) @@ -371,9 +402,21 @@ dentist <- function( nSample <- resolved$nSample sumStat <- .dentistResolveColumns(sumStat) windowMode <- arg_match(windowMode) - p <- as.list(environment()) if (nrow(sumStat) < minDim) { - return(.dentistCallSingle(sumStat$z, ldMat, nSample, p)) + return(.dentistCallSingle( + sumStat$z, + ldMat, + nSample, + pValueThreshold = pValueThreshold, + propSVD = propSVD, + gcControl = gcControl, + nIter = nIter, + gPvalueThreshold = gPvalueThreshold, + duprThreshold = duprThreshold, + ncpus = ncpus, + correctChenEtAlBug = correctChenEtAlBug, + seed = seed + )) } .dentistWindows( sumStat, @@ -382,7 +425,15 @@ dentist <- function( windowMode, windowSize, minDim, - p + pValueThreshold = pValueThreshold, + propSVD = propSVD, + gcControl = gcControl, + nIter = nIter, + gPvalueThreshold = gPvalueThreshold, + duprThreshold = duprThreshold, + ncpus = ncpus, + correctChenEtAlBug = correctChenEtAlBug, + seed = seed ) } @@ -1239,6 +1290,7 @@ mergeWindows <- function(dentistResultByWindow, windowDividedRes) { } # Lead-variant LD column. From X, compute just that column (avoid full p x p). +#' @importFrom checkmate assertMatrix .slalomLeadR <- function(zScore, R, X, leadIdx) { if (!is.null(X)) { if (!is.matrix(X)) { @@ -1246,9 +1298,7 @@ mergeWindows <- function(dentistResultByWindow, windowDividedRes) { } return(as.numeric(cor(X, X[, leadIdx]))) } - if (!is.matrix(R) || nrow(R) != ncol(R) || nrow(R) != length(zScore)) { - abort("R must be a square matrix matching the length of zScore.") - } + assertMatrix(R, nrows = length(zScore), ncols = length(zScore)) R[, leadIdx] } @@ -1434,8 +1484,10 @@ slalom <- function( #' df <- data.frame(cs_name = c("L1", "L2"), top_z = c(5, 3.5), #' p_value = c(1e-10, 1e-6)) #' autoDecision(df, highCorrCols = character(0)) +#' @importFrom checkmate assertCharacter #' @export autoDecision <- function(df, highCorrCols) { + assertCharacter(highCorrCols, any.missing = FALSE) # Identify top_cs topCsIndex <- which.max(abs(df$top_z)) df$top_cs <- FALSE @@ -2127,6 +2179,7 @@ raiss <- function( .raissLdBlocksPath(refPanel, knownZscores, ldMatrix, p) } +#' @importFrom checkmate assertNumeric #' @param zt Vector of known z scores. #' @param sigT Matrix of known linkage disequilibrium (LD) correlation. #' @param sigIT Correlation matrix with rows corresponding to unknown SNPs (to @@ -2150,9 +2203,9 @@ raissModel <- function( reportConditionNumber = FALSE ) { sigTInv <- invertMatRecursive(sigT, lamb, rcond) - if (!is.numeric(zt) || !is.numeric(sigT) || !is.numeric(sigIT)) { - abort("zt, sigT, and sigIT must be numeric.") - } + assertNumeric(zt) + assertNumeric(sigT) + assertNumeric(sigIT) if (batch) { conditionNumber <- if (reportConditionNumber) { rep(kappa(sigT, exact = TRUE, norm = "2"), nrow(sigIT)) @@ -2364,8 +2417,9 @@ varInBoundaries <- function(var, lamb) { return(var) } +#' @importFrom rlang try_fetch invertMat <- function(mat, lamb, rcond) { - tryCatch( + try_fetch( { # Modify the diagonal elements of mat diag(mat) <- 1 + lamb @@ -2373,7 +2427,7 @@ invertMat <- function(mat, lamb, rcond) { matInv <- ginv(mat, tol = rcond) return(matInv) }, - error = function(e) { + error = function(cnd) { # Second attempt with updated lamb and rcond in case of an error diag(mat) <- 1 + lamb * 1.1 matInv <- ginv(mat, tol = rcond * 1.1) @@ -2383,7 +2437,7 @@ invertMat <- function(mat, lamb, rcond) { } invertMatRecursive <- function(mat, lamb, rcond) { - tryCatch( + try_fetch( { # Modify the diagonal elements of mat diag(mat) <- 1 + lamb @@ -2391,7 +2445,7 @@ invertMatRecursive <- function(mat, lamb, rcond) { matInv <- ginv(mat, tol = rcond) return(matInv) }, - error = function(e) { + error = function(cnd) { # Recursive call with updated lamb and rcond in case of an error invertMat(mat, lamb * 1.1, rcond * 1.1) } @@ -2440,9 +2494,8 @@ invertMatEigen <- function(mat, tol = 1e-3) { #' \code{X} is provided. One of \code{"sample"} (default), #' \code{"population"}, or \code{"gcta"}. Ignored when \code{R} is provided #' directly. -#' @param ... Additional arguments passed to the underlying QC method -#' (\code{\link{dentistSingleWindow}} or \code{\link{slalom}}). -#' +#' @param methodArgs Optional named list of options passed to the +#' underlying QC method (\code{slalom} or \code{dentistSingleWindow}). #' @return A data frame with at least a logical \code{outlier} column indicating #' which variants are identified as outliers. The remaining columns depend on #' the method used. @@ -2462,21 +2515,27 @@ ldMismatchQc <- function( nSample = NULL, method = c("slalom", "dentist"), ldMethod = "sample", - ... + methodArgs = list() ) { method <- arg_match(method) if (method == "dentist") { - qcResults <- dentistSingleWindow( - zScore, - R = R, - X = X, - nSample = nSample, - ldMethod = ldMethod, - ... + callArgs <- list_modify( + list( + zScore, + R = R, + X = X, + nSample = nSample, + ldMethod = ldMethod + ), + !!!methodArgs ) - return(qcResults) + return(exec(dentistSingleWindow, !!!callArgs)) } else { - qcResults <- slalom(zScore, R = R, X = X, ldMethod = ldMethod, ...) + callArgs <- list_modify( + list(zScore, R = R, X = X, ldMethod = ldMethod), + !!!methodArgs + ) + qcResults <- exec(slalom, !!!callArgs) # Standardize output: slalom uses "outliers", rename to "outlier" for # consistency result <- qcResults$data @@ -2577,6 +2636,7 @@ effectiveN <- function(nCase, nControl) { #' krigingOutlierQc( #' zScore = rnorm(20), R = R, n = 415, variantIds = colnames(X)) #' @export +#' @importFrom checkmate assertMatrix krigingOutlierQc <- function( zScore, R, @@ -2587,9 +2647,7 @@ krigingOutlierQc <- function( ) { zScore <- as.numeric(zScore) m <- length(zScore) - if (is.null(R) || !is.matrix(R) || nrow(R) != m || ncol(R) != m) { - abort("krigingOutlierQc requires a square LD matrix aligned to zScore.") - } + assertMatrix(R, nrows = m, ncols = m, .var.name = "R (LD matrix)") if (missing(n) || length(n) != 1L || is.na(n) || !is.finite(n) || n <= 0) { abort("krigingOutlierQc requires a single positive sample size 'n'.") } @@ -3282,6 +3340,7 @@ krigingOutlierQc <- function( } # Per-row sanity checks: sequence the guarded checks, accumulating the audit. +#' @importFrom purrr list_modify .applySanityChecks <- function( df, coerceNumeric = TRUE, @@ -3300,25 +3359,25 @@ krigingOutlierQc <- function( } r <- .scCoerceNumeric(df, coerceNumeric) df <- r$df - audit <- modifyList(audit, r$audit) + audit <- list_modify(audit, !!!r$audit) r <- .scNormalizeChr(df, normalizeChr, dropNonstandardChr) df <- r$df - audit <- modifyList(audit, r$audit) + audit <- list_modify(audit, !!!r$audit) r <- .scDropMissData(df, dropMissData) df <- r$df - audit <- modifyList(audit, r$audit) + audit <- list_modify(audit, !!!r$audit) r <- .scDropPOutOfRange(df, dropPOutOfRange) df <- r$df - audit <- modifyList(audit, r$audit) + audit <- list_modify(audit, !!!r$audit) r <- .scClampSmallP(df, clampSmallP, smallPFloor) df <- r$df - audit <- modifyList(audit, r$audit) + audit <- list_modify(audit, !!!r$audit) r <- .scDropZeroEffect(df, dropZeroEffect) df <- r$df - audit <- modifyList(audit, r$audit) + audit <- list_modify(audit, !!!r$audit) r <- .scDropNonpositiveSe(df, dropNonpositiveSe) df <- r$df - audit <- modifyList(audit, r$audit) + audit <- list_modify(audit, !!!r$audit) list(df = df, audit = audit) } @@ -4375,20 +4434,20 @@ krigingOutlierQc <- function( lbl <- init$lbl san <- .qcStepSanity(df, opts, lbl) df <- san$df - entryAudit <- modifyList(entryAudit, san$audit) + entryAudit <- list_modify(entryAudit, !!!san$audit) eff <- .qcStepEffectiveN(df, opts, lbl) df <- eff$df entryAudit$nSource <- eff$nSource opts <- eff$opts cf <- .qcStepContentFilters(df, opts, lbl) df <- cf$df - entryAudit <- modifyList(entryAudit, cf$audit) + entryAudit <- list_modify(entryAudit, !!!cf$audit) der <- .qcStepDerive(df, opts, entryAudit) df <- der$df entryAudit <- der$entryAudit ks <- .qcStepKeepSkip(df, opts) df <- ks$df - entryAudit <- modifyList(entryAudit, ks$audit) + entryAudit <- list_modify(entryAudit, !!!ks$audit) list( df = df, entryAudit = entryAudit, @@ -4417,22 +4476,22 @@ krigingOutlierQc <- function( } harm <- .qcHarmonizeEntry(df, ldSketch, opts, lbl) df <- harm$df - entryAudit <- modifyList(entryAudit, harm$audit) - qcCount <- modifyList(qcCount, harm$counts) + entryAudit <- list_modify(entryAudit, !!!harm$audit) + qcCount <- list_modify(qcCount, !!!harm$counts) scr <- .qcStepScreen(df, opts) df <- scr$df - entryAudit <- modifyList(entryAudit, scr$audit) + entryAudit <- list_modify(entryAudit, !!!scr$audit) kr <- .qcKrigingFlip(df, ldSketch, opts, lbl) df <- kr$df - entryAudit <- modifyList(entryAudit, kr$audit) + entryAudit <- list_modify(entryAudit, !!!kr$audit) qcCount$krigingFlipped <- kr$count mm <- .qcMismatchQc(df, ldSketch, opts, lbl) df <- mm$df - entryAudit <- modifyList(entryAudit, mm$audit) + entryAudit <- list_modify(entryAudit, !!!mm$audit) qcCount$mismatchRemoved <- mm$count imp <- .qcRaissImputeStep(df, ldSketch, opts, lbl, qcCount) df <- imp$df - entryAudit <- modifyList(entryAudit, imp$audit) + entryAudit <- list_modify(entryAudit, !!!imp$audit) qcCount <- imp$qcCount .qcEmitRollup(entryAudit, qcCount, opts, pre$nStudyIn, nrow(df), lbl) entryAudit$variantsOut <- nrow(df) @@ -4457,8 +4516,8 @@ krigingOutlierQc <- function( if (is.null(ldSketch)) { return(NULL) } - chrom <- unlist(map(entries, .entryChrom), use.names = FALSE) - pos <- unlist(map(entries, .entryPos), use.names = FALSE) + chrom <- unname(list_c(map(entries, .entryChrom))) + pos <- unname(list_c(map(entries, .entryPos))) ok <- !is.na(chrom) & !is.na(pos) chrom <- chrom[ok] pos <- pos[ok] @@ -4508,7 +4567,7 @@ krigingOutlierQc <- function( if (nRanges == 0L) { return(.emptySketch(ldSketch)) } - ids <- unlist(map(entries, .entrySnpIds), use.names = FALSE) + ids <- unname(list_c(map(entries, .entrySnpIds))) if (length(ids) == 0L) { return(ldSketch) } @@ -4533,13 +4592,9 @@ krigingOutlierQc <- function( # --- summaryStatsQc orchestration helpers ---------------------------------- # Validate input class + per-entry MAF/INFO column availability. +#' @importFrom checkmate assertMultiClass .ssqcCheckEntries <- function(sumstats, mafCutoff, infoCutoff) { - if ( - !methods::is(sumstats, "QtlSumStats") && - !methods::is(sumstats, "GwasSumStats") - ) { - abort("summaryStatsQc requires a QtlSumStats or GwasSumStats input.") - } + assertMultiClass(sumstats, c("QtlSumStats", "GwasSumStats")) for (i in seq_len(nrow(sumstats))) { cols <- colnames(S4Vectors::mcols(.collectionEntry(sumstats, i))) # mafCutoff no longer pre-aborts on a missing frequency: .cfMaf @@ -4582,39 +4637,69 @@ krigingOutlierQc <- function( } # Build the per-entry QC options list from the captured call parameters. -.ssqcBuildOpts <- function(p) { - optNames <- c( - "removeIndels", - "removeStrandAmbiguous", - "mafCutoff", - "macCutoff", - "imissCutoff", - "infoCutoff", - "nCutoff", - "skipRegion", - "zMismatchQc", - "alleleFlipKriging", - "effectiveN", - "impute", - "imputeOpts", - "matchMinProp", - "coerceNumeric", - "normalizeChr", - "dropNonstandardChr", - "dropMissData", - "dropPOutOfRange", - "clampSmallP", - "smallPFloor", - "dropZeroEffect", - "dropNonpositiveSe" +.ssqcBuildOpts <- function( + removeIndels, + removeStrandAmbiguous, + mafCutoff, + macCutoff, + imissCutoff, + infoCutoff, + nCutoff, + skipRegion, + zMismatchQc, + alleleFlipKriging, + effectiveN, + impute, + imputeOpts, + matchMinProp, + coerceNumeric, + normalizeChr, + dropNonstandardChr, + dropMissData, + dropPOutOfRange, + clampSmallP, + smallPFloor, + dropZeroEffect, + dropNonpositiveSe, + keepVariants, + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip +) { + # Built explicitly rather than by subsetting a captured environment with a + # character vector: a renamed argument is now an error, not a NULL entry. + opts <- list( + removeIndels = removeIndels, + removeStrandAmbiguous = removeStrandAmbiguous, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff, + infoCutoff = infoCutoff, + nCutoff = nCutoff, + skipRegion = skipRegion, + zMismatchQc = zMismatchQc, + alleleFlipKriging = alleleFlipKriging, + effectiveN = effectiveN, + impute = impute, + imputeOpts = imputeOpts, + matchMinProp = matchMinProp, + coerceNumeric = coerceNumeric, + normalizeChr = normalizeChr, + dropNonstandardChr = dropNonstandardChr, + dropMissData = dropMissData, + dropPOutOfRange = dropPOutOfRange, + clampSmallP = clampSmallP, + smallPFloor = smallPFloor, + dropZeroEffect = dropZeroEffect, + dropNonpositiveSe = dropNonpositiveSe ) - opts <- p[optNames] - opts$keepVariants <- as.character(p$keepVariants) + opts$keepVariants <- as.character(keepVariants) opts$screen <- .resolveScreenMetric( - p$pipCutoffToSkip, - p$absZCutoffToSkip, - p$bfCutoffToSkip, - p$logBfCutoffToSkip + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip ) opts$nCase <- NULL opts$nControl <- NULL @@ -4704,7 +4789,7 @@ krigingOutlierQc <- function( # and from the summary statistics alike. ldSketch <- .ssqcPrunePanel( getLdSketch(sumstats), - .panelCutoffs(opts), + .panelCutoffs(opts$mafCutoff, opts$macCutoff, opts$imissCutoff), "summaryStatsQc" ) refGenome <- getGenome(sumstats) @@ -4728,36 +4813,61 @@ krigingOutlierQc <- function( } # Assemble the qcInfo record (echoed options + per-entry audits). -.ssqcBuildQcInfo <- function(p, entryAudits) { - optNames <- c( - "removeIndels", - "removeStrandAmbiguous", - "mafCutoff", - "macCutoff", - "imissCutoff", - "infoCutoff", - "nCutoff", - "pipCutoffToSkip", - "absZCutoffToSkip", - "bfCutoffToSkip", - "logBfCutoffToSkip", - "zMismatchQc", - "alleleFlipKriging", - "effectiveN", - "impute", - "coerceNumeric", - "normalizeChr", - "dropNonstandardChr", - "dropMissData", - "dropPOutOfRange", - "clampSmallP", - "smallPFloor", - "dropZeroEffect", - "dropNonpositiveSe" - ) +.ssqcBuildQcInfo <- function( + entryAudits, + removeIndels, + removeStrandAmbiguous, + mafCutoff, + macCutoff, + imissCutoff, + infoCutoff, + nCutoff, + pipCutoffToSkip, + absZCutoffToSkip, + bfCutoffToSkip, + logBfCutoffToSkip, + zMismatchQc, + alleleFlipKriging, + effectiveN, + impute, + coerceNumeric, + normalizeChr, + dropNonstandardChr, + dropMissData, + dropPOutOfRange, + clampSmallP, + smallPFloor, + dropZeroEffect, + dropNonpositiveSe +) { list( timestamp = NA_character_, - options = p[optNames], + options = list( + removeIndels = removeIndels, + removeStrandAmbiguous = removeStrandAmbiguous, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff, + infoCutoff = infoCutoff, + nCutoff = nCutoff, + pipCutoffToSkip = pipCutoffToSkip, + absZCutoffToSkip = absZCutoffToSkip, + bfCutoffToSkip = bfCutoffToSkip, + logBfCutoffToSkip = logBfCutoffToSkip, + zMismatchQc = zMismatchQc, + alleleFlipKriging = alleleFlipKriging, + effectiveN = effectiveN, + impute = impute, + coerceNumeric = coerceNumeric, + normalizeChr = normalizeChr, + dropNonstandardChr = dropNonstandardChr, + dropMissData = dropMissData, + dropPOutOfRange = dropPOutOfRange, + clampSmallP = clampSmallP, + smallPFloor = smallPFloor, + dropZeroEffect = dropZeroEffect, + dropNonpositiveSe = dropNonpositiveSe + ), entryAudit = entryAudits ) } @@ -5002,10 +5112,64 @@ summaryStatsQc <- function( zMismatchQc <- arg_match(zMismatchQc) .ssqcCheckEntries(sumstats, mafCutoff, infoCutoff) .ssqcCheckPanelCutoffs(mafCutoff, macCutoff, imissCutoff) - p <- as.list(environment()) - opts <- .ssqcBuildOpts(p) + opts <- .ssqcBuildOpts( + removeIndels = removeIndels, + removeStrandAmbiguous = removeStrandAmbiguous, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff, + infoCutoff = infoCutoff, + nCutoff = nCutoff, + skipRegion = skipRegion, + zMismatchQc = zMismatchQc, + alleleFlipKriging = alleleFlipKriging, + effectiveN = effectiveN, + impute = impute, + imputeOpts = imputeOpts, + matchMinProp = matchMinProp, + coerceNumeric = coerceNumeric, + normalizeChr = normalizeChr, + dropNonstandardChr = dropNonstandardChr, + dropMissData = dropMissData, + dropPOutOfRange = dropPOutOfRange, + clampSmallP = clampSmallP, + smallPFloor = smallPFloor, + dropZeroEffect = dropZeroEffect, + dropNonpositiveSe = dropNonpositiveSe, + keepVariants = keepVariants, + pipCutoffToSkip = pipCutoffToSkip, + absZCutoffToSkip = absZCutoffToSkip, + bfCutoffToSkip = bfCutoffToSkip, + logBfCutoffToSkip = logBfCutoffToSkip + ) res <- .ssqcRunEntries(sumstats, opts) - qcInfo <- .ssqcBuildQcInfo(p, res$entryAudits) + qcInfo <- .ssqcBuildQcInfo( + res$entryAudits, + removeIndels = removeIndels, + removeStrandAmbiguous = removeStrandAmbiguous, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff, + infoCutoff = infoCutoff, + nCutoff = nCutoff, + pipCutoffToSkip = pipCutoffToSkip, + absZCutoffToSkip = absZCutoffToSkip, + bfCutoffToSkip = bfCutoffToSkip, + logBfCutoffToSkip = logBfCutoffToSkip, + zMismatchQc = zMismatchQc, + alleleFlipKriging = alleleFlipKriging, + effectiveN = effectiveN, + impute = impute, + coerceNumeric = coerceNumeric, + normalizeChr = normalizeChr, + dropNonstandardChr = dropNonstandardChr, + dropMissData = dropMissData, + dropPOutOfRange = dropPOutOfRange, + clampSmallP = clampSmallP, + smallPFloor = smallPFloor, + dropZeroEffect = dropZeroEffect, + dropNonpositiveSe = dropNonpositiveSe + ) # From the PRUNED panel, not the input's: narrowing the original again # would hand back a sketch the panel filter never reached. newLdSketch <- .subsetSketchToIds(res$ldSketch, res$newEntries) diff --git a/R/tupleSelectors.R b/R/tupleSelectors.R index 1aa329e18..9e0310569 100644 --- a/R/tupleSelectors.R +++ b/R/tupleSelectors.R @@ -74,7 +74,7 @@ .fmrTupleLabel <- function(side, ident, block = NULL) { fields <- compact(c(ident, list(block = block))) fields <- fields[!map_lgl(fields, is.na)] - shown <- str_c(names(fields), "='", unlist(fields), "'") + shown <- str_c(names(fields), "='", map_chr(fields, as.character), "'") glue("{side} ({str_flatten(shown, ', ')})") } @@ -119,6 +119,7 @@ # TwasWeights accessors. Returns an error when no row matches; returns # the single row index when the collection has exactly one row and any # selector argument was omitted. +#' @importFrom checkmate assertVector .tupleSelectRow <- function( x, study, @@ -149,16 +150,10 @@ ) abort(msg) } - if ( - length(study) != 1L || - length(context) != 1L || - length(trait) != 1L || - length(method) != 1L - ) { - abort( - "`study`, `context`, `trait`, and `method` must each be length 1." - ) - } + assertVector(study, len = 1L) + assertVector(context, len = 1L) + assertVector(trait, len = 1L) + assertVector(method, len = 1L) .tupleMatchQtl(x, study, context, trait, method) } @@ -183,6 +178,7 @@ # index of a GwasFineMappingResult collection. `region` may be NULL when # the (study, method) pair maps to a single row; otherwise it disambiguates # among per-block rows of a genome-wide collection. +#' @importFrom checkmate assertVector .tupleSelectRowGwasFmr <- function(x, study, method, region = NULL) { if (nrow(x) == 0L) { abort("GwasFineMappingResult has no rows.") @@ -201,12 +197,9 @@ ) abort(msg) } - if (length(study) != 1L || length(method) != 1L) { - abort("`study` and `method` must each be length 1.") - } - if (!is.null(region) && length(region) != 1L) { - abort("`region` must be length 1 when supplied.") - } + assertVector(study, len = 1L) + assertVector(method, len = 1L) + assertVector(region, len = 1L, null.ok = TRUE) .tupleMatchGwas(x, study, method, region) } @@ -387,13 +380,15 @@ cols } +#' @importFrom checkmate checkClass .validateTraitPosColumn <- function(object) { if (!is_in("traitPos", .tupleColumnNames(object))) { return(character(0)) } traitPos <- .tupleColumn(object, "traitPos") - if (!methods::is(traitPos, "GRanges")) { - return("'traitPos' column must be a GRanges") + res <- checkClass(traitPos, "GRanges") + if (!isTRUE(res)) { + return(str_c("'traitPos' column ", res)) } if (length(traitPos) != nrow(object)) { return("'traitPos' column must have one range per row") @@ -541,6 +536,7 @@ # list(done, result) on a successful single-select, else list(done = FALSE, sel) # carrying the selection error (or NULL when no selector was given). # @noRd +#' @importFrom rlang try_fetch .fmrTrySingle <- function( x, study, @@ -559,7 +555,7 @@ if (!anySelector) { return(list(done = FALSE, sel = NULL)) } - sel <- tryCatch( + sel <- try_fetch( .fmrSelectEntry( x, study = study, @@ -568,7 +564,7 @@ method = method, region = region ), - error = function(e) e + error = function(cnd) cnd ) if (!inherits(sel, "error")) { return(list(done = TRUE, result = onSingle(sel, ...))) @@ -730,8 +726,7 @@ type = c("data.frame", "GRanges"), signalCutoff = 0.025, minPurity = NULL, - raw = FALSE, - ... + raw = FALSE ) { tl <- .fmrPartsTopLoci(parts) # raw = TRUE returns the stored canonical table verbatim: every variant, @@ -748,7 +743,7 @@ } # @noRd -.fmrRowPip <- function(parts, ...) { +.fmrRowPip <- function(parts) { tl <- .fmrPartsTopLoci(parts) if (nrow(tl) == 0L || !is_in("pip", names(tl))) { return(numeric(0)) @@ -757,7 +752,7 @@ } # @noRd -.fmrRowMarginalEffects <- function(parts, maxPval = NULL, ...) { +.fmrRowMarginalEffects <- function(parts, maxPval = NULL) { tl <- .fmrPartsTopLoci(parts) if (nrow(tl) == 0L) { return(.projectMarginalView(tl)) @@ -771,7 +766,7 @@ } # @noRd -.fmrRowCs <- function(parts, coverage = 0.95, minPurity = NULL, ...) { +.fmrRowCs <- function(parts, coverage = 0.95, minPurity = NULL) { tl <- .fmrPartsTopLoci(parts) if (nrow(tl) == 0L) { return(.projectPosteriorView(tl)) @@ -805,7 +800,7 @@ } # @noRd -.fmrRowLbf <- function(parts, ...) { +.fmrRowLbf <- function(parts) { lbf <- .asLbfMatrix(getSusieFit(parts)) vids <- .fmrPartsVariantIds(parts) if (is.null(lbf) || ncol(lbf) != length(vids)) { @@ -817,17 +812,17 @@ } # @noRd -.fmrRowCredibleSetSummary <- function(parts, coverage = 0.95, ...) { +.fmrRowCredibleSetSummary <- function(parts, coverage = 0.95) { .csSummaryFit(.fmrPartsTopLoci(parts), getSusieFit(parts), coverage) } # @noRd -.fmrRowFsusieCredibleBand <- function(parts, ...) { +.fmrRowFsusieCredibleBand <- function(parts) { .fsusieCredibleBandFit(getSusieFit(parts)) } # @noRd -.fmrRowFsusieAffectedRegions <- function(parts, ...) { +.fmrRowFsusieAffectedRegions <- function(parts) { .fsusieAffectedRegionsFit( getSusieFit(parts), topLoci = .fmrPartsTopLoci(parts) @@ -835,7 +830,7 @@ } # @noRd -.fmrRowResolveWeights <- function(parts, ...) { +.fmrRowResolveWeights <- function(parts) { empty <- list(variantIds = character(0), weights = numeric(0)) # The topLoci posterior view projects the effect to `beta`; use it as the # per-variant weight, aligned with variant_id. @@ -882,7 +877,7 @@ # The weight vector aligned to the row's variant ids -- what resolveWeights # produced from a TwasWeightsRow. # @noRd -.twrRowResolveWeights <- function(parts, ...) { +.twrRowResolveWeights <- function(parts) { empty <- list(variantIds = character(0), weights = numeric(0)) vids <- .twrPartsVariantIds(parts) w <- getWeights(parts) @@ -914,11 +909,11 @@ # The per-variant weight vector of one row, whichever weight source it came # from. # @noRd -.rowResolveWeights <- function(parts, ...) { +.rowResolveWeights <- function(parts) { if (methods::is(parts, "TwasWeightsRow")) { - return(.twrRowResolveWeights(parts, ...)) + return(.twrRowResolveWeights(parts)) } - .fmrRowResolveWeights(parts, ...) + .fmrRowResolveWeights(parts) } # The cross-validated / per-method fits of one row, or NULL for a diff --git a/R/twasWeights.R b/R/twasWeights.R index 8d7a4dfd4..c742fa5f4 100644 --- a/R/twasWeights.R +++ b/R/twasWeights.R @@ -46,25 +46,23 @@ setClass( # region/traitPos provenance, joint* column types, tuple uniqueness, and the # optional ldSketch. Returns TRUE or a character vector of error messages. # @noRd +#' @importFrom checkmate makeAssertCollection assertNames .validateTwasWeights <- function(object) { - errors <- .twasValidateRequiredCols(object) - if (length(errors) == 0L) { - errors <- .twasValidateColumns(object) - } - errors <- c(errors, .twasValidateLdSketch(object)) - if (length(errors) == 0L) TRUE else errors -} - -# Required key/entry columns must all be present. -# @noRd -.twasValidateRequiredCols <- function(object) { - required <- c("study", "context", "trait", "method") - missingCols <- setdiff(required, .tupleColumnNames(object)) - if (length(missingCols) > 0L) { - str_c("missing columns: ", str_flatten(missingCols, ", ")) - } else { - character() + coll <- makeAssertCollection() + assertNames( + .tupleColumnNames(object), + must.include = c("study", "context", "trait", "method"), + what = "colnames", + .var.name = "mcols", + add = coll + ) + # The checks below read those columns; running them on an object missing + # them reports the consequence rather than the cause. + if (!coll$isEmpty()) { + return(coll$getMessages()) } + coll$push(.twasValidateColumns(object)) + coll$getMessages() } # Column-level checks that run only once the required columns are present. @@ -127,14 +125,6 @@ setClass( } } -# The ldSketch slot's class union enforces its type, so there is nothing left -# for validity to check. -# @noRd -.twasValidateLdSketch <- function(object) { - character() -} - - # ============================================================================= #' @title Create a TwasWeights Collection Object @@ -169,6 +159,8 @@ setClass( #' tw <- TwasWeights(study = "s1", context = "brain", trait = "gene1", #' method = "susie", entry = list(twe)) #' tw +#' @importFrom checkmate assertCharacter assert checkList +#' @importFrom checkmate checkClass #' @export TwasWeights <- function( study, @@ -182,6 +174,20 @@ TwasWeights <- function( traitPos = NULL, ldSketch = NULL ) { + assertCharacter(study, any.missing = FALSE) + assertCharacter(context, any.missing = FALSE) + assertCharacter(trait, any.missing = FALSE) + assertCharacter(method, any.missing = FALSE) + # `entry` is documented as "List / SimpleList"; SimpleList is S4 and + # fails checkList, so this must be an or-combination. + assert( + checkList(entry), + checkClass(entry, "SimpleList"), + .var.name = "entry" + ) + assertCharacter(jointStudies, null.ok = TRUE) + assertCharacter(jointContexts, null.ok = TRUE) + assertCharacter(jointTraits, null.ok = TRUE) n <- .twasCheckRowLengths(study, context, trait, method, entry) entry <- map(entry, .asTwRowPayload) .checkRowPayloads(entry, "TwasWeightsRow", "TWAS-weight") @@ -576,7 +582,8 @@ setMethod("show", "TwasWeights", function(object) { susie = list( fn = "susie_weights", impl = "susieWeights", - args = list(refine = FALSE, L = 10) + # No fitting defaults: susieWeights extracts from a supplied fit. + args = list() ), susieAsh = list( fn = "susie_ash_weights", @@ -659,7 +666,8 @@ setMethod("show", "TwasWeights", function(object) { mvsusie = list( fn = "mvsusie_weights", impl = "mvsusieWeights", - args = list(L = 10) + # No fitting defaults: mvsusieWeights extracts from a supplied fit. + args = list() ), mrmash = list( fn = "mrmash_weights", @@ -967,13 +975,14 @@ setMethod("show", "TwasWeights", function(object) { names(weightMethods[["susie_weights"]]), fitArgNames )] - # modifyList (not list_modify): a NULL user arg should UNSET the key here. - susieInfArgs <- modifyList( + # A NULL user arg should UNSET the key, so compact() first -- that is the + # same rule the old modifyList relied on, now stated rather than implied. + susieInfArgs <- list_modify( list(convergence_method = "pip"), - weightMethods[["susie_inf_weights"]][setdiff( + !!!compact(weightMethods[["susie_inf_weights"]][setdiff( names(weightMethods[["susie_inf_weights"]]), fitArgNames - )] + )]) ) fits <- fitSusieInfThenSusie( X, @@ -1061,18 +1070,24 @@ setMethod("show", "TwasWeights", function(object) { # Per-fold priors bound to a multivariate fitter's camelCase args (mr.mash # data-driven matrices / mvsusie reweighted mixture prior for fold `j`). # @noRd -.twasFoldPriors <- function(args, method, j, cvArgs) { +.twasFoldPriors <- function( + args, + method, + j, + dataDrivenPriorMatricesCv, + reweightedMixturePriorCv +) { if ( - !is.null(cvArgs$data_driven_priorMatricesCv) && + !is.null(dataDrivenPriorMatricesCv) && is_in(method, c("mrmash_weights", "mrmashWeights")) ) { - args$dataDrivenPriorMatrices <- cvArgs$data_driven_priorMatricesCv[[j]] + args$dataDrivenPriorMatrices <- dataDrivenPriorMatricesCv[[j]] } if ( - !is.null(cvArgs$reweightedMixturePriorCv) && + !is.null(reweightedMixturePriorCv) && is_in(method, c("mvsusie_weights", "mvsusieWeights")) ) { - args$prior_variance <- cvArgs$reweightedMixturePriorCv[[j]] + args$prior_variance <- reweightedMixturePriorCv[[j]] } args } @@ -1080,11 +1095,17 @@ setMethod("show", "TwasWeights", function(object) { # One fold's multivariate weight fit; returns list(W, fit). # @noRd .twasFoldMultivariate <- function(method, fnName, args, Xtr, Ytr, j, ctx) { - args <- .twasFoldPriors(args, method, j, ctx$cvArgs) + args <- .twasFoldPriors( + args, + method, + j, + ctx$dataDrivenPriorMatricesCv, + ctx$reweightedMixturePriorCv + ) if (isTRUE(ctx$retainFits) && is_in("retainFit", names(formals(fnName)))) { args$retainFit <- TRUE } - callArgs <- c(list(X = Xtr, Y = Ytr), args) + callArgs <- .twasWeightCallArgs(fnName, list(X = Xtr, Y = Ytr), args) W <- if (ctx$verbose < 2) { .quietEval(exec(fnName, !!!callArgs)) } else { @@ -1170,7 +1191,10 @@ setMethod("show", "TwasWeights", function(object) { #' seed is scoped to the call, so the session RNG is left as it was found. #' \code{NULL} (default) does not seed at all and uses the historical #' parallel default. -#' @param ... Additional arguments forwarded to the per-method weight learners. +#' @param dataDrivenPriorMatricesCv Optional list, one element per fold, of +#' data-driven prior matrices for the mr.mash learner. +#' @param reweightedMixturePriorCv Optional list, one element per fold, of +#' reweighted mixture priors for the mvSuSiE learner. #' @return A list with the following components: #' \itemize{ #' \item `samplePartition`: A dataframe showing the sample partitioning used @@ -1204,6 +1228,8 @@ setMethod("show", "TwasWeights", function(object) { #' Y <- multiTraitData$Y #' twasWeightsCv(X, Y[, 1, drop = FALSE], fold = 3, #' weightMethods = list(susie_weights = list())) +#' @importFrom checkmate assertDataFrame assertNumber assertInt +#' @importFrom checkmate assertFlag assertCount #' @export twasWeightsCv <- function( X, @@ -1217,11 +1243,35 @@ twasWeightsCv <- function( verbose = 1, retainFits = FALSE, seed = NULL, - ... + dataDrivenPriorMatricesCv = NULL, + reweightedMixturePriorCv = NULL ) { - p <- as.list(environment()) - p$cvArgs <- list(...) - .twasWeightsCvImpl(p) + # X / Y / fold are asserted downstream in .cvPrepareData; these are the + # arguments nothing else checks. + assertDataFrame(samplePartitions, null.ok = TRUE) + # NOT assertCount: `Inf` is the "no cap" sentinel (jointEngine passes it + # when cfg$maxCvVariants is unset), and .cvSubsampleVariants relies on + # `ncol(X) <= maxNumVariants` being FALSE for it. + assertNumber(maxNumVariants, lower = 1, null.ok = TRUE) + assertInt(numThreads) + assertCount(verbose) + assertFlag(retainFits) + assertInt(seed, null.ok = TRUE) + .twasWeightsCvImpl( + X = X, + Y = Y, + fold = fold, + samplePartitions = samplePartitions, + weightMethods = weightMethods, + maxNumVariants = maxNumVariants, + variantsToKeep = variantsToKeep, + numThreads = numThreads, + verbose = verbose, + retainFits = retainFits, + seed = seed, + dataDrivenPriorMatricesCv = dataDrivenPriorMatricesCv, + reweightedMixturePriorCv = reweightedMixturePriorCv + ) } # Multivariate weight methods (snake + camel) fit on the whole Y for a fold; @@ -1236,16 +1286,30 @@ twasWeightsCv <- function( "mvsusieWeights" ) -# twasWeightsCv worker. `p` is the captured public arguments plus `cvArgs` -# (the `...`). With no weightMethods the caller only wants the fold partition. +# twasWeightsCv worker. With no weightMethods the caller only wants the fold +# partition. # @noRd -.twasWeightsCvImpl <- function(p) { - weightMethods <- if (is.character(p$weightMethods)) { - .twasMethodLookup(p$weightMethods) +.twasWeightsCvImpl <- function( + X, + Y, + fold, + samplePartitions, + weightMethods, + maxNumVariants, + variantsToKeep, + numThreads, + verbose, + retainFits, + seed, + dataDrivenPriorMatricesCv, + reweightedMixturePriorCv +) { + weightMethods <- if (is.character(weightMethods)) { + .twasMethodLookup(weightMethods) } else { - p$weightMethods + weightMethods } - if (is.null(p$seed) && !exists(".Random.seed") && p$verbose >= 1) { + if (is.null(seed) && !exists(".Random.seed") && verbose >= 1) { inform(str_c( "! No seed set. Pass `seed=` or call ", "set.seed() for reproducibility." @@ -1253,40 +1317,41 @@ twasWeightsCv <- function( } if (is.null(weightMethods)) { res <- .crossValidateWeights( - p$X, - p$Y, - fold = p$fold, - samplePartitions = p$samplePartitions, + X, + Y, + fold = fold, + samplePartitions = samplePartitions, fitFold = .cvNoopFitFold, - numThreads = p$numThreads, - maxNumVariants = p$maxNumVariants, - variantsToKeep = p$variantsToKeep, - retainFits = p$retainFits, - verbose = p$verbose, - seed = p$seed + numThreads = numThreads, + maxNumVariants = maxNumVariants, + variantsToKeep = variantsToKeep, + retainFits = retainFits, + verbose = verbose, + seed = seed ) return(list(samplePartition = res$samplePartition)) } cvFitCtx <- list( weightMethods = weightMethods, multivariateWeightMethods = .twasCvMultivariateMethods, - cvArgs = p$cvArgs, - retainFits = p$retainFits, - verbose = p$verbose + dataDrivenPriorMatricesCv = dataDrivenPriorMatricesCv, + reweightedMixturePriorCv = reweightedMixturePriorCv, + retainFits = retainFits, + verbose = verbose ) .crossValidateWeights( - p$X, - p$Y, - fold = p$fold, - samplePartitions = p$samplePartitions, + X, + Y, + fold = fold, + samplePartitions = samplePartitions, fitFold = .weightFitFold, fitFoldCtx = cvFitCtx, - numThreads = p$numThreads, - maxNumVariants = p$maxNumVariants, - variantsToKeep = p$variantsToKeep, - retainFits = p$retainFits, - verbose = p$verbose, - seed = p$seed + numThreads = numThreads, + maxNumVariants = maxNumVariants, + variantsToKeep = variantsToKeep, + retainFits = retainFits, + verbose = verbose, + seed = seed ) } @@ -1377,7 +1442,11 @@ twasWeightsCv <- function( # Multivariate fit: one call producing the full variants x features matrix. # @noRd .twasFitMultivariate <- function(fnName, args, ctx) { - call <- c(list(X = ctx$Xfiltered, Y = ctx$Y), args) + call <- .twasWeightCallArgs( + fnName, + list(X = ctx$Xfiltered, Y = ctx$Y), + args + ) weightsMatrix <- if (ctx$verbose < 2) { .quietEval(exec(fnName, !!!call)) } else { @@ -1397,7 +1466,11 @@ twasWeightsCv <- function( weightsMatrix <- matrix(0, nrow = ncol(ctx$Xfiltered), ncol = ncol(ctx$Y)) methodFit <- NULL for (k in seq_len(ncol(ctx$Y))) { - call <- c(list(X = ctx$Xfiltered, y = ctx$Y[, k]), args) + call <- .twasWeightCallArgs( + fnName, + list(X = ctx$Xfiltered, y = ctx$Y[, k]), + args + ) weightsVector <- if (ctx$verbose < 2) { .quietEval(exec(fnName, !!!call)) } else { @@ -1550,6 +1623,8 @@ twasWeightsCv <- function( #' @importFrom rlang !!! abort warn inform arg_match cnd_signal .data #' @importFrom glue glue #' @importFrom tictoc tic toc +#' @importFrom checkmate assertString assertInt assertFlag assertCount +#' @importFrom checkmate assert checkList checkCharacter learnTwasWeights <- function( X, Y, @@ -1567,21 +1642,50 @@ learnTwasWeights <- function( verbose = 1, seed = NULL ) { - .learnTwasWeightsImpl(as.list(environment())) + assertString(study) + assertString(context) + assertString(trait) + assertInt(numThreads) + assertFlag(retainFits) + assertFlag(standardized) + assertString(dataType, null.ok = TRUE) + assertCount(verbose) + assertInt(seed, null.ok = TRUE) + # weightMethods is documented as a named list OR a character vector. + assert( + checkList(weightMethods), + checkCharacter(weightMethods), + .var.name = "weightMethods" + ) + .learnTwasWeightsImpl( + X = X, + Y = Y, + weightMethods = weightMethods, + study = study, + context = context, + trait = trait, + numThreads = numThreads, + fittedModels = fittedModels, + retainFits = retainFits, + retainFitDetail = retainFitDetail, + standardized = standardized, + dataType = dataType, + ldSketch = ldSketch, + verbose = verbose, + seed = seed + ) } # Validate X/Y shapes; coerce a vector Y to a one-column matrix. Returns Y. # @noRd +#' @importFrom checkmate assert assertMatrix checkAtomicVector checkMatrix .twasValidateXY <- function(X, Y) { - if (!is.matrix(X) || (!is.matrix(Y) && !is.vector(Y))) { - abort("X must be a matrix and Y must be a matrix or a vector.") - } + assertMatrix(X) + assert(checkMatrix(Y), checkAtomicVector(Y), .var.name = "Y") if (is.vector(Y)) { Y <- matrix(Y, ncol = 1) } - if (nrow(X) != nrow(Y)) { - abort("The number of rows in X and Y must be the same.") - } + assertMatrix(Y, nrows = nrow(X)) Y } @@ -1643,53 +1747,68 @@ learnTwasWeights <- function( # learnTwasWeights worker: validate, resolve methods, fit each, and assemble the # TwasWeights collection. `p` is the captured public arguments. # @noRd -.learnTwasWeightsImpl <- function(p) { - .applySeed(p$seed) - retainFitDetail <- p$retainFitDetail +.learnTwasWeightsImpl <- function( + X, + Y, + weightMethods, + study, + context, + trait, + numThreads, + fittedModels, + retainFits, + retainFitDetail, + standardized, + dataType, + ldSketch, + verbose, + seed +) { + .applySeed(seed) retainFitDetail <- arg_match(retainFitDetail, c("slim", "full")) - Y <- .twasValidateXY(p$X, p$Y) - weightMethods <- if (is.character(p$weightMethods)) { - .twasMethodLookup(p$weightMethods) + Y <- .twasValidateXY(X, Y) + weightMethods <- if (is.character(weightMethods)) { + .twasMethodLookup(weightMethods) } else { - p$weightMethods + weightMethods } - validColumns <- .nonzeroVarColumns(p$X) - Xfiltered <- as.matrix(p$X[, validColumns, drop = FALSE]) + validColumns <- .nonzeroVarColumns(X) + Xfiltered <- as.matrix(X[, validColumns, drop = FALSE]) weightMethods <- .prepareSusieWeightMethods( Xfiltered, Y, weightMethods, - p$fittedModels + fittedModels ) ctx <- list( - X = p$X, + X = X, Y = Y, Xfiltered = Xfiltered, validColumns = validColumns, - study = p$study, - context = p$context, - trait = p$trait, - retainFits = p$retainFits, + study = study, + context = context, + trait = trait, + retainFits = retainFits, retainFitDetail = retainFitDetail, - standardized = p$standardized, - dataType = p$dataType, - verbose = p$verbose, - rngSeed = p$seed + standardized = standardized, + dataType = dataType, + verbose = verbose, + rngSeed = seed ) weightsList <- .twasFitAllMethods( weightMethods, ctx, - .twasResolveCores(p$numThreads) + .twasResolveCores(numThreads) ) - weightsList <- .twasApplyRownames(weightsList, p$X) - rows <- .buildTwasWeightEntries(weightsList, .twasVariantIds(p$X), ctx) + weightsList <- .twasApplyRownames(weightsList, X) + rows <- .buildTwasWeightEntries(weightsList, .twasVariantIds(X), ctx) TwasWeights( study = rows$study, context = rows$context, trait = rows$trait, method = rows$method, entry = rows$entry, - ldSketch = p$ldSketch + ldSketch = ldSketch ) } @@ -1724,7 +1843,15 @@ learnTwasWeights <- function( #' tw <- TwasWeights(study = "s1", context = "brain", trait = "g1", #' method = "susie", entry = list(twe)) #' twasPredict(X, tw) +#' @importFrom checkmate assert checkList checkClass +#' @importFrom checkmate assertList twasPredict <- function(X, weightsList) { + # The body branches on TwasWeights, so this is a list OR that S4 class. + assert( + checkList(weightsList), + checkClass(weightsList, "TwasWeights"), + .var.name = "weightsList" + ) if (is(weightsList, "TwasWeights")) { # Per-row weights vector/matrix payloads. Use the method name as key # for compatibility with the legacy snake_case "_predicted" @@ -1861,10 +1988,21 @@ estimateSparsity <- function(weightResults) { .twasFoldRow(k, cvFolds[[k]], sampleNames) } +# Route a caller's per-method arguments into a weight function. A wrapper's +# own formals (a pre-fit, retainFit, initPriorSd, ...) bind by name; anything +# else is a tool option and goes in the wrapper's `methodArgs` list, so an +# unknown option errors inside the wrapper rather than vanishing. Wrappers +# with no `methodArgs` formal get everything by name, and R reports an unused +# argument -- which is the point. +# @noRd +.twasWeightCallArgs <- function(fnName, baseArgs, userArgs) { + c(baseArgs, .splitMethodArgs(fnName, userArgs)) +} + # One univariate fold's weight column for outcome `k` (quiet unless verbose). # @noRd .twasFitColWeight <- function(k, ctx, fnName, Xtr, Ytr, args) { - callArgs <- c(list(X = Xtr, y = Ytr[, k]), args) + callArgs <- .twasWeightCallArgs(fnName, list(X = Xtr, y = Ytr[, k]), args) w <- if (ctx$verbose < 2) { .quietEval(exec(fnName, !!!callArgs)) } else { diff --git a/R/twasWeightsPipeline.R b/R/twasWeightsPipeline.R index 1f6b1d866..12495f391 100644 --- a/R/twasWeightsPipeline.R +++ b/R/twasWeightsPipeline.R @@ -6,10 +6,10 @@ # column (delegates to the generic `.rbindCollections`, which unions columns # and pads a side lacking an optional column such as joint* / region). # @noRd +#' @importFrom checkmate assertClass .rbindTwasWeights <- function(a, b, ldSketch = NULL) { - if (!is(a, "TwasWeights") || !is(b, "TwasWeights")) { - abort(".rbindTwasWeights expects two TwasWeights inputs.") - } + assertClass(a, "TwasWeights") + assertClass(b, "TwasWeights") # Carry forward every column (joint*, region, ...) and reconcile the # collection-level slots via the shared combine. .combineTupleCollections(list(a, b), ldSketch, ".rbindTwasWeights") @@ -126,13 +126,10 @@ combineTwasWeights <- function(..., ldSketch = NULL) { weights <- if (is.matrix(wList[[1L]])) { exec(rbind, !!!wList) } else { - unlist(wList, use.names = FALSE) + unname(list_c(wList)) } twasWeightsRow( - variantIds = unlist( - map(entries, .twrPartsVariantIds), - use.names = FALSE - ), + variantIds = unname(list_c(map(entries, .twrPartsVariantIds))), weights = weights, fits = set_names(map(entries, getFits), regionLabels), cvResult = .twasRegionCvDf(entries, regionLabels), @@ -148,6 +145,7 @@ combineTwasWeights <- function(..., ldSketch = NULL) { # otherwise the partition the per-fold priors were computed on) NULL input # returns all-NULL, preserving the supplied samplePartition. # @noRd +#' @importFrom checkmate assertClass .unpackMashPrior <- function(mashPrior, samplePartition = NULL) { if (is.null(mashPrior)) { return(list( @@ -156,9 +154,7 @@ combineTwasWeights <- function(..., ldSketch = NULL) { samplePartition = samplePartition )) } - if (!is(mashPrior, "MashPrior")) { - abort("`mashPrior` must be a MashPrior object (see ?MashPrior).") - } + assertClass(mashPrior, "MashPrior") cvFits <- getCvFits(mashPrior) perFold <- if (!is.null(cvFits)) cvFits$perFoldFits else NULL sp <- samplePartition @@ -366,7 +362,7 @@ combineTwasWeights <- function(..., ldSketch = NULL) { .twasNormalizeListMethods <- function(methods) { methodList <- list() for (tk in names(methods)) { - base <- tryCatch(.twasMethodLookup(tk), error = function(e) NULL) + base <- try_fetch(.twasMethodLookup(tk), error = function(cnd) NULL) if (is.null(base)) { # Fine-mapping-only / unknown token with no learner default: keep # as-is (the downstream capability gate produces the message). @@ -374,7 +370,7 @@ combineTwasWeights <- function(..., ldSketch = NULL) { next } snake <- names(base)[[1L]] - merged <- modifyList(base[[snake]], methods[[tk]]) + merged <- list_modify(base[[snake]], !!!compact(methods[[tk]])) attr(merged, "impl") <- attr(base[[snake]], "impl") methodList[[snake]] <- merged } @@ -654,6 +650,7 @@ combineTwasWeights <- function(..., ldSketch = NULL) { # A supplied fineMappingResult is mandatory (fine-mapping is never re-fit) and # must be a FineMappingResult. # @noRd +#' @importFrom checkmate assertClass .twasRequireFmResult <- function(fineMappingResult, fmTokens) { if (is.null(fineMappingResult)) { fmStr <- str_flatten(unique(fmTokens), ", ") @@ -665,9 +662,7 @@ combineTwasWeights <- function(..., ldSketch = NULL) { ) abort(msg) } - if (!is(fineMappingResult, "FineMappingResultBase")) { - abort("`fineMappingResult` must be a FineMappingResult or NULL.") - } + assertClass(fineMappingResult, "FineMappingResultBase") invisible(NULL) } @@ -772,13 +767,12 @@ combineTwasWeights <- function(..., ldSketch = NULL) { # suitable for `learnTwasWeights`. Pulls the trimmedFit from the matching # entry. Returns a (possibly empty) list. # @noRd +#' @importFrom checkmate assertClass .twasFineMappingFits <- function(fineMappingResult, study, context, trait) { if (is.null(fineMappingResult)) { return(list()) } - if (!is(fineMappingResult, "FineMappingResultBase")) { - abort("`fineMappingResult` must be a FineMappingResult or NULL.") - } + assertClass(fineMappingResult, "FineMappingResultBase") out <- list() methods <- as.character(fineMappingResult$method) for (canonical in c("susie", "susieInf", "susieAsh", "mvsusie", "fsusie")) { @@ -1024,7 +1018,11 @@ combineTwasWeights <- function(..., ldSketch = NULL) { #' @param maxCvVariants Maximum number of variants for CV. Default -1 (no #' limit). #' @param cvThreads Threads for CV parallelism. Default 1. -#' @param cvWeightMethods Optional override of methods used for CV. +#' @param cvWeightMethods Optional override of which methods are +#' cross-validated, as a character vector of tokens or a named method +#' list. \code{NULL} (default) cross-validates every method that +#' produced non-zero weights; a method whose weights are all zero is +#' excluded with a warning either way. #' @param ensemble Logical. Compute SR-TWAS ensemble weights. Default #' \code{TRUE}. #' @param ensembleR2Threshold Minimum CV R-squared for ensemble inclusion. @@ -1153,55 +1151,193 @@ setMethod( ) { naAction <- arg_match(naAction) retainFitDetail <- arg_match(retainFitDetail) - p <- as.list(environment()) - p$dots <- list(...) - .twasPipelineQtlDataset(p) + .twasPipelineQtlDataset( + data = data, + methods = methods, + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + xvarCutoff = xvarCutoff, + imissCutoff = imissCutoff, + keepIndel = keepIndel, + keepSamples = keepSamples, + keepVariants = keepVariants, + jointRegions = jointRegions, + jointSpecification = jointSpecification, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + mashPrior = mashPrior, + cvFolds = cvFolds, + samplePartition = samplePartition, + fitFullData = fitFullData, + cvWeightMethods = cvWeightMethods, + maxCvVariants = maxCvVariants, + cvThreads = cvThreads, + ensemble = ensemble, + ensembleR2Threshold = ensembleR2Threshold, + ensembleSolver = ensembleSolver, + ensembleAlpha = ensembleAlpha, + estimatePi = estimatePi, + retainFit = retainFit, + retainFitDetail = retainFitDetail, + dataType = dataType, + naAction = naAction, + verbose = verbose, + seed = seed + ) } ) # ---- QtlDataset pipeline worker + phase helpers ---------------------------- -.twasPipelineQtlDataset <- function(p) { - .twasQdsCheckRegionCisWindow(p) - p$xRegions <- .makeXRegions(p$region, p$jointRegions) +.twasPipelineQtlDataset <- function( + data, + methods, + contexts, + traitId, + region, + cisWindow, + mafCutoff, + macCutoff, + xvarCutoff, + imissCutoff, + keepIndel, + keepSamples, + keepVariants, + jointRegions, + jointSpecification, + fineMappingResult, + twasWeights, + mashPrior, + cvFolds, + samplePartition, + fitFullData, + cvWeightMethods, + maxCvVariants, + cvThreads, + ensemble, + ensembleR2Threshold, + ensembleSolver, + ensembleAlpha, + estimatePi, + retainFit, + retainFitDetail, + dataType, + naAction, + verbose, + seed +) { + .twasQdsCheckRegionCisWindow(region, cisWindow) + xRegions <- .makeXRegions(region, jointRegions) # Per-call filter overrides replace the construct-time slot values on a # validated copy. Variant QC is a data property applied identically to # fine-mapping and TWAS -- there is no TWAS-specific variant filter. - p$data <- .qtlApplyFilterOverrides( - p$data, - p$mafCutoff, - p$macCutoff, - p$xvarCutoff, - p$imissCutoff, - p$keepIndel, - p$keepSamples, - p$keepVariants - ) - p$parsedJointSpec <- parseJointSpecification(p$jointSpecification, p$data) - p$norm <- .twasNormalizeMethods(p$methods) - .twasCheckMethodCapabilities(p$norm$tokens, "QtlDataset") - .twasCheckFineMappingMethods( - p$norm$tokens, - p$fineMappingResult, - "QtlDataset" + data <- .qtlApplyFilterOverrides( + data, + mafCutoff, + macCutoff, + xvarCutoff, + imissCutoff, + keepIndel, + keepSamples, + keepVariants + ) + parsedJointSpec <- parseJointSpecification(jointSpecification, data) + norm <- .twasNormalizeMethods(methods) + .twasCheckMethodCapabilities(norm$tokens, "QtlDataset") + .twasCheckFineMappingMethods(norm$tokens, fineMappingResult, "QtlDataset") + .twasQdsCheckFitFull(fitFullData, cvFolds) + mash <- .twasQdsUnpackMash(mashPrior, samplePartition, norm) + samplePartition <- mash$samplePartition + dataDrivenPriorMatricesCv <- mash$dataDrivenPriorMatricesCv + norm <- mash$norm + joint <- .twasQdsJointPhase( + parsedJointSpec, + norm, + data, + contexts, + traitId, + cisWindow, + dataType, + verbose, + xRegions, + retainFit, + retainFitDetail, + seed ) - .twasQdsCheckFitFull(p) - p <- .twasQdsUnpackMash(p) - joint <- .twasQdsJointPhase(p) if (joint$done) { return(joint$result) } - p$norm <- joint$norm - p$jointResult <- joint$result - p <- .twasQdsResolveGrid(p) - .twasQdsAssemble(.twasQdsDispatch(p), p$jointResult) + # The joint phase consumes the mrmash token; `norm` comes back holding + # only the methods that still have to go through the per-tuple loop. + norm <- joint$norm + grid <- .twasQdsResolveGrid(data, contexts, traitId, region, norm$tokens) + study <- grid$study + useCtx <- grid$useCtx + allTraits <- grid$allTraits + marker <- .twasQdsMarker( + cvFolds = cvFolds, + samplePartition = samplePartition, + fitFullData = fitFullData, + dataType = dataType, + retainFitDetail = retainFitDetail, + ensemble = ensemble, + ensembleR2Threshold = ensembleR2Threshold, + ensembleSolver = ensembleSolver, + ensembleAlpha = ensembleAlpha, + maxCvVariants = maxCvVariants, + cvThreads = cvThreads, + estimatePi = estimatePi, + verbose = verbose, + seed = seed, + cvWeightMethods = cvWeightMethods + ) + tw <- if (grid$multivariate) { + .twasRunMultivariateGrid( + allTraits, + marker, + .twasQdsGridCtx( + xRegions = xRegions, + data = data, + norm = norm, + useCtx = useCtx, + fineMappingResult = fineMappingResult, + dataDrivenPriorMatricesCv = dataDrivenPriorMatricesCv, + cisWindow = cisWindow, + verbose = verbose + ) + ) + } else { + .twasQdsUnivariateEngine( + study = study, + useCtx = useCtx, + allTraits = allTraits, + marker = marker, + data = data, + xRegions = xRegions, + norm = norm, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + dataDrivenPriorMatricesCv = dataDrivenPriorMatricesCv, + cisWindow = cisWindow, + naAction = naAction, + verbose = verbose + ) + } + .twasQdsAssemble(tw, joint$result) } # `cisWindow` expands a trait's own coordinates; `region` is literal. Supplying # both signals a misunderstanding -> reject. # @noRd -.twasQdsCheckRegionCisWindow <- function(p) { - if (!is.null(p$region) && !is.null(p$cisWindow)) { +.twasQdsCheckRegionCisWindow <- function( + region, + cisWindow +) { + if (!is.null(region) && !is.null(cisWindow)) { msg <- glue( "twasWeightsPipeline(QtlDataset): specify either `region` or ", "`cisWindow`, not both. `cisWindow` expands each trait's own ", @@ -1214,8 +1350,11 @@ setMethod( # fitFullData = FALSE (CV-only) is meaningful only with cross-validation. # @noRd -.twasQdsCheckFitFull <- function(p) { - if (!isTRUE(p$fitFullData) && p$cvFolds <= 1L) { +.twasQdsCheckFitFull <- function( + fitFullData, + cvFolds +) { + if (!isTRUE(fitFullData) && cvFolds <= 1L) { msg <- glue( "twasWeightsPipeline: fitFullData = FALSE requires ", "cross-validation (cvFolds > 1)." @@ -1229,11 +1368,13 @@ setMethod( # args, the per-fold priors + fold partition into the CV machinery. Returns the # updated parameter bundle. # @noRd -.twasQdsUnpackMash <- function(p) { - mp <- .unpackMashPrior(p$mashPrior, p$samplePartition) - p$samplePartition <- mp$samplePartition - p$dataDrivenPriorMatricesCv <- mp$dataDrivenPriorMatricesCv - if (!is.null(p$mashPrior) && !is_in("mrmash", p$norm$tokens)) { +.twasQdsUnpackMash <- function( + mashPrior, + samplePartition, + norm +) { + mp <- .unpackMashPrior(mashPrior, samplePartition) + if (!is.null(mashPrior) && !is_in("mrmash", norm$tokens)) { msg <- glue( "`mashPrior` was supplied but 'mrmash' is not among `methods`; ", "the data-driven prior is ignored." @@ -1242,11 +1383,15 @@ setMethod( } if ( !is.null(mp$fullPrior) && - is_in("mrmash_weights", names(p$norm$methodList)) + is_in("mrmash_weights", names(norm$methodList)) ) { - p$norm$methodList$mrmash_weights$dataDrivenPriorMatrices <- mp$fullPrior + norm$methodList$mrmash_weights$dataDrivenPriorMatrices <- mp$fullPrior } - p + list( + samplePartition = mp$samplePartition, + dataDrivenPriorMatricesCv = mp$dataDrivenPriorMatricesCv, + norm = norm + ) } # Explicit jointSpecification path: run the per-spec axis dispatcher for @@ -1254,25 +1399,38 @@ setMethod( # with `result`; otherwise `norm` is the mrmash-stripped normalization for the # per-tuple loop below. # @noRd -.twasQdsJointPhase <- function(p) { - if (length(p$parsedJointSpec) == 0L) { - return(list(done = FALSE, result = NULL, norm = p$norm)) +.twasQdsJointPhase <- function( + parsedJointSpec, + norm, + data, + contexts, + traitId, + cisWindow, + dataType, + verbose, + xRegions, + retainFit, + retainFitDetail, + seed +) { + if (length(parsedJointSpec) == 0L) { + return(list(done = FALSE, result = NULL, norm = norm)) } jointResult <- .twasDispatchJointSpecsQtlDataset( - p$parsedJointSpec, - p$data, - intersect(p$norm$tokens, "mrmash"), - p$contexts, - p$traitId, - p$cisWindow, - p$dataType, - p$verbose, - xRegions = p$xRegions, - retainFit = p$retainFit, - retainFitDetail = p$retainFitDetail, - seed = p$seed - ) - keep <- setdiff(p$norm$tokens, intersect(p$norm$tokens, "mrmash")) + parsedJointSpec, + data, + intersect(norm$tokens, "mrmash"), + contexts, + traitId, + cisWindow, + dataType, + verbose, + xRegions = xRegions, + retainFit = retainFit, + retainFitDetail = retainFitDetail, + seed = seed + ) + keep <- setdiff(norm$tokens, intersect(norm$tokens, "mrmash")) if (length(keep) == 0L) { if (is.null(jointResult)) { msg <- glue( @@ -1284,7 +1442,7 @@ setMethod( } return(list(done = TRUE, result = jointResult)) } - norm <- p$norm + norm <- norm norm$tokens <- keep keepKeys <- which( is_in(str_remove(names(norm$methodList), "(_weights|Weights)$"), keep) @@ -1296,21 +1454,23 @@ setMethod( # Resolve the (context, trait) grid + multivariate flag and build the joint- # pipeline marker + shared grid context. Returns the updated parameter bundle. # @noRd -.twasQdsResolveGrid <- function(p) { - p$study <- getStudy(p$data) - p$useCtx <- .twasQdsResolveContexts(p$data, p$contexts) - p$allTraits <- .twasQdsResolveTraits( - p$data, - p$useCtx, - p$traitId, - p$region +.twasQdsResolveGrid <- function( + data, + contexts, + traitId, + region, + tokens +) { + study <- getStudy(data) + useCtx <- .twasQdsResolveContexts(data, contexts) + allTraits <- .twasQdsResolveTraits(data, useCtx, traitId, region) + .twasCheckMultivariateY(tokens, length(allTraits), length(useCtx)) + list( + study = study, + useCtx = useCtx, + allTraits = allTraits, + multivariate = any(map_lgl(tokens, .twasIsMultivariateToken)) ) - p$nCtx <- length(p$useCtx) - .twasCheckMultivariateY(p$norm$tokens, length(p$allTraits), p$nCtx) - p$multivariate <- any(map_lgl(p$norm$tokens, .twasIsMultivariateToken)) - p$marker <- .twasQdsMarker(p) - p$twasGridCtx <- .twasQdsGridCtx(p) - p } # Selected contexts (all when NULL; else validated against the dataset). @@ -1342,7 +1502,7 @@ setMethod( traitId = traitId, region = region ) - allTraits <- unique(unlist(perCtxTraits)) + allTraits <- unique(list_c(perCtxTraits)) if (length(allTraits) == 0L) { abort("twasWeightsPipeline(QtlDataset): no traits selected.") } @@ -1351,25 +1511,42 @@ setMethod( # Joint-pipeline marker carrying the CV / ensemble config for the engine. # @noRd -.twasQdsMarker <- function(p) { +.twasQdsMarker <- function( + cvFolds, + samplePartition, + fitFullData, + dataType, + retainFitDetail, + ensemble, + ensembleR2Threshold, + ensembleSolver, + ensembleAlpha, + maxCvVariants, + cvThreads, + estimatePi, + verbose, + seed, + cvWeightMethods +) { new( "TwasJointPipeline", config = list( - cvFolds = p$cvFolds, - samplePartition = p$samplePartition, - fitFullData = p$fitFullData, - dataType = p$dataType, - retainFitDetail = p$retainFitDetail, + cvFolds = cvFolds, + samplePartition = samplePartition, + fitFullData = fitFullData, + dataType = dataType, + retainFitDetail = retainFitDetail, standardized = FALSE, - ensemble = p$ensemble, - ensembleR2Threshold = p$ensembleR2Threshold, - ensembleSolver = p$ensembleSolver, - ensembleAlpha = p$ensembleAlpha, - maxCvVariants = p$maxCvVariants, - cvThreads = p$cvThreads, - estimatePi = p$estimatePi, - verbose = p$verbose, - seed = p$seed, + ensemble = ensemble, + ensembleR2Threshold = ensembleR2Threshold, + ensembleSolver = ensembleSolver, + ensembleAlpha = ensembleAlpha, + cvWeightMethods = cvWeightMethods, + maxCvVariants = maxCvVariants, + cvThreads = cvThreads, + estimatePi = estimatePi, + verbose = verbose, + seed = seed, ldSketch = NULL ) ) @@ -1377,46 +1554,69 @@ setMethod( # Shared grid context consumed by both dispatch paths. # @noRd -.twasQdsGridCtx <- function(p) { +.twasQdsGridCtx <- function( + xRegions, + data, + norm, + useCtx, + fineMappingResult, + dataDrivenPriorMatricesCv, + cisWindow, + verbose +) { list( - xRegions = p$xRegions, - data = p$data, - norm = p$norm, - useCtx = p$useCtx, - fineMappingResult = p$fineMappingResult, - dataDrivenPriorMatricesCv = p$dataDrivenPriorMatricesCv, - cisWindow = p$cisWindow, - verbose = p$verbose + xRegions = xRegions, + data = data, + norm = norm, + useCtx = useCtx, + fineMappingResult = fineMappingResult, + dataDrivenPriorMatricesCv = dataDrivenPriorMatricesCv, + cisWindow = cisWindow, + verbose = verbose ) } -# Top-level dispatch: multivariate joint grid vs univariate engine path. -# @noRd -.twasQdsDispatch <- function(p) { - if (p$multivariate) { - return(.twasRunMultivariateGrid(p$allTraits, p$marker, p$twasGridCtx)) - } - .twasQdsUnivariateEngine(p) -} - # Univariate methods ROUTED THROUGH THE ENGINE: one 1-condition group per # (context, trait), per region -> the SAME per-method fitter (+ ensemble layer # for >= 2 methods + resume cache) as the joint paths, merged across regions. # @noRd -.twasQdsUnivariateEngine <- function(p) { +.twasQdsUnivariateEngine <- function( + study, + useCtx, + allTraits, + marker, + data, + xRegions, + norm, + fineMappingResult, + twasWeights, + dataDrivenPriorMatricesCv, + cisWindow, + naAction, + verbose +) { univCell <- .lookupJointCell("univariate", "individual") scope <- list( - studies = p$study, - contexts = set_names(list(p$useCtx), p$study), - traits = set_names(list(p$allTraits), p$study) + studies = study, + contexts = set_names(list(useCtx), study), + traits = set_names(list(allTraits), study) ) - labs <- map_chr(p$xRegions, .twasRegionLabel) + labs <- map_chr(xRegions, .twasRegionLabel) perRegion <- map( - seq_along(p$xRegions), + seq_along(xRegions), .twasQdsUnivRegion, univCell = univCell, - p = p, - scope = scope + scope = scope, + marker = marker, + data = data, + xRegions = xRegions, + norm = norm, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + dataDrivenPriorMatricesCv = dataDrivenPriorMatricesCv, + cisWindow = cisWindow, + naAction = naAction, + verbose = verbose ) keep <- !map_lgl(perRegion, is.null) .twasMergeRegionResults(perRegion[keep], labs[keep]) @@ -1424,18 +1624,28 @@ setMethod( # Per-region engine args for the univariate path. # @noRd -.twasQdsUnivArgs <- function(p, bi) { +.twasQdsUnivArgs <- function( + bi, + xRegions, + norm, + fineMappingResult, + twasWeights, + dataDrivenPriorMatricesCv, + cisWindow, + naAction, + verbose +) { list( - methodList = p$norm$methodList, - fineMappingResult = p$fineMappingResult, - cache = p$twasWeights, - dataDrivenPriorMatricesCv = p$dataDrivenPriorMatricesCv, - cisWindow = p$cisWindow, - region = p$xRegions[[bi]], + methodList = norm$methodList, + fineMappingResult = fineMappingResult, + cache = twasWeights, + dataDrivenPriorMatricesCv = dataDrivenPriorMatricesCv, + cisWindow = cisWindow, + region = xRegions[[bi]], regionIndex = bi, - nRegions = length(p$xRegions), - naAction = p$naAction, - verbose = p$verbose + nRegions = length(xRegions), + naAction = naAction, + verbose = verbose ) } @@ -1493,34 +1703,118 @@ setMethod( ... ) { retainFitDetail <- arg_match(retainFitDetail) - p <- as.list(environment()) - p$dots <- list(...) - .twasPipelineQtlSumStats(p) + .twasPipelineQtlSumStats( + data = data, + methods = methods, + contexts = contexts, + traitId = traitId, + jointSpecification = jointSpecification, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + retainFit = retainFit, + retainFitDetail = retainFitDetail, + dataType = dataType, + verbose = verbose, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ) } ) # ---- QtlSumStats pipeline worker + phase helpers --------------------------- -.twasPipelineQtlSumStats <- function(p) { +.twasPipelineQtlSumStats <- function( + data, + methods, + contexts, + traitId, + jointSpecification, + fineMappingResult, + twasWeights, + retainFit, + retainFitDetail, + dataType, + verbose, + mafCutoff, + macCutoff, + imissCutoff +) { # summaryStatsQc() is mandatory before twasWeightsPipeline for SumStats # input; it also drops variants not present in the ldSketch, so every # entry's SNP set is a subset of the ldSketch panel by the time we get here. - .twasAssertQcd(p$data) - p$parsedJointSpec <- parseJointSpecification(p$jointSpecification, p$data) - tm <- .twasSumStatsMethodTokens(p$methods) - p$tokens <- tm$tokens - p$methodArgs <- tm$methodArgs - .twasCheckMethodCapabilities(p$tokens, "QtlSumStats") - .twasCheckFineMappingMethods(p$tokens, p$fineMappingResult, "QtlSumStats") - joint <- .twasQssJointPhase(p) + .twasAssertQcd(data) + parsedJointSpec <- parseJointSpecification(jointSpecification, data) + tm <- .twasSumStatsMethodTokens(methods) + tokens <- tm$tokens + methodArgs <- tm$methodArgs + .twasCheckMethodCapabilities(tokens, "QtlSumStats") + .twasCheckFineMappingMethods(tokens, fineMappingResult, "QtlSumStats") + joint <- .twasQssJointPhase( + parsedJointSpec, + data, + tokens, + methodArgs, + contexts, + traitId, + dataType, + verbose, + retainFit, + retainFitDetail, + mafCutoff, + macCutoff, + imissCutoff + ) if (joint$done) { return(joint$result) } - p$tokens <- joint$tokens - p$methodArgs <- joint$methodArgs - p <- .twasQssSelectAndPartition(p) - rows <- c(.twasQssUnivariateRows(p), .twasQssMultivariateRows(p)) - .twasQssAssemble(rows, joint$result, p) + # The joint phase consumes the mrmash token; what it hands back is the + # remainder that still has to go through the per-tuple loop. + tokens <- joint$tokens + methodArgs <- joint$methodArgs + part <- .twasQssSelectAndPartition(data, tokens, contexts, traitId) + studyCol <- part$studyCol + contextCol <- part$contextCol + traitCol <- part$traitCol + selRows <- part$selRows + multivariateTokens <- part$multivariateTokens + univariateTokens <- part$univariateTokens + ldSketch <- part$ldSketch + rows <- c( + .twasQssUnivariateRows( + selRows, + univariateTokens = univariateTokens, + studyCol = studyCol, + contextCol = contextCol, + traitCol = traitCol, + data = data, + ldSketch = ldSketch, + twasWeights = twasWeights, + dataType = dataType, + fineMappingResult = fineMappingResult, + methodArgs = methodArgs, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ), + .twasQssMultivariateRows( + selRows, + multivariateTokens = multivariateTokens, + studyCol = studyCol, + contextCol = contextCol, + traitCol = traitCol, + data = data, + ldSketch = ldSketch, + methodArgs = methodArgs, + retainFitDetail = retainFitDetail, + fineMappingResult = fineMappingResult, + dataType = dataType, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + ) + ) + .twasQssAssemble(rows, joint$result, ldSketch) } # Normalize the methods argument into (tokens, methodArgs). The default set @@ -1558,30 +1852,44 @@ setMethod( # methodArgs): `done` requests an early return with `result`; otherwise the # remaining (non-mrmash) tokens + args continue through the per-tuple loop. # @noRd -.twasQssJointPhase <- function(p) { - if (length(p$parsedJointSpec) == 0L) { +.twasQssJointPhase <- function( + parsedJointSpec, + data, + tokens, + methodArgs, + contexts, + traitId, + dataType, + verbose, + retainFit, + retainFitDetail, + mafCutoff, + macCutoff, + imissCutoff +) { + if (length(parsedJointSpec) == 0L) { return(list( done = FALSE, result = NULL, - tokens = p$tokens, - methodArgs = p$methodArgs + tokens = tokens, + methodArgs = methodArgs )) } jointResult <- .twasDispatchJointSpecsQtlSumStats( - p$parsedJointSpec, - p$data, - intersect(p$tokens, "mrmash"), - p$contexts, - p$traitId, - p$dataType, - p$verbose, - retainFit = p$retainFit, - retainFitDetail = p$retainFitDetail, - mafCutoff = p$mafCutoff %||% 0, - macCutoff = p$macCutoff %||% 0, - imissCutoff = p$imissCutoff %||% 1 - ) - keep <- setdiff(p$tokens, "mrmash") + parsedJointSpec, + data, + intersect(tokens, "mrmash"), + contexts, + traitId, + dataType, + verbose, + retainFit = retainFit, + retainFitDetail = retainFitDetail, + mafCutoff = mafCutoff %||% 0, + macCutoff = macCutoff %||% 0, + imissCutoff = imissCutoff %||% 1 + ) + keep <- setdiff(tokens, "mrmash") if (length(keep) == 0L) { if (is.null(jointResult)) { abort("twasWeightsPipeline(QtlSumStats): no joint fits produced.") @@ -1592,7 +1900,7 @@ setMethod( done = FALSE, result = jointResult, tokens = keep, - methodArgs = p$methodArgs[keep] + methodArgs = methodArgs[keep] ) } @@ -1600,28 +1908,53 @@ setMethod( # attach the LD sketch, and enforce the multivariate >=2-contexts rule. Returns # the updated parameter bundle. # @noRd -.twasQssSelectAndPartition <- function(p) { - p$studyCol <- as.character(p$data$study) - p$contextCol <- as.character(p$data$context) - p$traitCol <- as.character(p$data$trait) - p$selRows <- .twasQssSelectRows(p) - isMv <- map_lgl(p$tokens, .twasIsMultivariateToken) - p$multivariateTokens <- p$tokens[isMv] - p$univariateTokens <- p$tokens[!isMv] - p$ldSketch <- getLdSketch(p$data) - .twasQssCheckMultivariate(p) - p +.twasQssSelectAndPartition <- function(data, tokens, contexts, traitId) { + studyCol <- as.character(data$study) + contextCol <- as.character(data$context) + traitCol <- as.character(data$trait) + selRows <- .twasQssSelectRows( + data, + contextCol, + traitCol, + contexts, + traitId + ) + isMv <- map_lgl(tokens, .twasIsMultivariateToken) + multivariateTokens <- tokens[isMv] + univariateTokens <- tokens[!isMv] + .twasQssCheckMultivariate( + multivariateTokens, + selRows, + studyCol, + contextCol, + traitCol + ) + list( + studyCol = studyCol, + contextCol = contextCol, + traitCol = traitCol, + selRows = selRows, + multivariateTokens = multivariateTokens, + univariateTokens = univariateTokens, + ldSketch = getLdSketch(data) + ) } # Row indices matching the contexts / traitId filters (error if none). # @noRd -.twasQssSelectRows <- function(p) { - selRows <- seq_len(nrow(p$data)) - if (!is.null(p$contexts)) { - selRows <- selRows[is_in(p$contextCol[selRows], p$contexts)] +.twasQssSelectRows <- function( + data, + contextCol, + traitCol, + contexts, + traitId +) { + selRows <- seq_len(nrow(data)) + if (!is.null(contexts)) { + selRows <- selRows[is_in(contextCol[selRows], contexts)] } - if (!is.null(p$traitId)) { - selRows <- selRows[is_in(p$traitCol[selRows], p$traitId)] + if (!is.null(traitId)) { + selRows <- selRows[is_in(traitCol[selRows], traitId)] } if (length(selRows) == 0L) { msg <- glue( @@ -1635,18 +1968,24 @@ setMethod( # Multivariate methods require at least two contexts within some (study, trait). # @noRd -.twasQssCheckMultivariate <- function(p) { - if (length(p$multivariateTokens) == 0L) { +.twasQssCheckMultivariate <- function( + multivariateTokens, + selRows, + studyCol, + contextCol, + traitCol +) { + if (length(multivariateTokens) == 0L) { return(invisible(NULL)) } groupKey <- str_c( - p$studyCol[p$selRows], - p$traitCol[p$selRows], + studyCol[selRows], + traitCol[selRows], sep = "||" ) - perGroupNCtx <- map_int(split(p$contextCol[p$selRows], groupKey), length) + perGroupNCtx <- map_int(split(contextCol[selRows], groupKey), length) if (all(perGroupNCtx < 2L)) { - mvStr <- str_flatten(p$multivariateTokens, ", ") + mvStr <- str_flatten(multivariateTokens, ", ") msg <- glue( "twasWeightsPipeline(QtlSumStats): multivariate method(s) ", "{mvStr} require at least two contexts per (study, trait); the ", @@ -1724,17 +2063,20 @@ setMethod( # Run a weight function, warning (with `errPrefix`) and returning NULL on error. # @noRd +#' @importFrom rlang try_fetch .twasTryWeights <- function(fn, stat, ldMat, userArgs, errPrefix) { - tryCatch( + try_fetch( { wfn <- get(fn, mode = "function") - wArgs <- c(list(stat = stat, LD = ldMat), userArgs) + wArgs <- .twasWeightCallArgs( + fn, + list(stat = stat, LD = ldMat), + userArgs + ) exec(wfn, !!!wArgs) }, - error = function(e) { - eMsg <- conditionMessage(e) - msg <- glue("{errPrefix}{eMsg}") - warn(msg) + error = function(cnd) { + warn(errPrefix, parent = cnd) NULL } ) @@ -1742,26 +2084,72 @@ setMethod( # ---- Univariate dispatch: per (study, context, trait), per method ---------- -.twasQssUnivariateRows <- function(p) { - if (length(p$univariateTokens) == 0L) { +.twasQssUnivariateRows <- function( + selRows, + univariateTokens, + studyCol, + contextCol, + traitCol, + data, + ldSketch, + twasWeights, + dataType, + fineMappingResult, + methodArgs, + mafCutoff, + macCutoff, + imissCutoff +) { + if (length(univariateTokens) == 0L) { return(list()) } - list_flatten(map(p$selRows, .twasQssUnivariateRowsForEntry, p = p)) + list_flatten(map( + selRows, + .twasQssUnivariateRowsForEntry, + univariateTokens = univariateTokens, + studyCol = studyCol, + contextCol = contextCol, + traitCol = traitCol, + data = data, + ldSketch = ldSketch, + twasWeights = twasWeights, + dataType = dataType, + fineMappingResult = fineMappingResult, + methodArgs = methodArgs, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + )) } # Cached + freshly-fitted rows for one sumstats entry. Resume cache: pull cached # entries up front and reduce the per-entry fit work to the un-cached tokens. # @noRd -.twasQssUnivariateRowsForEntry <- function(i, p) { - st <- p$studyCol[i] - ctx <- p$contextCol[i] - tr <- p$traitCol[i] +.twasQssUnivariateRowsForEntry <- function( + i, + univariateTokens, + studyCol, + contextCol, + traitCol, + data, + ldSketch, + twasWeights, + dataType, + fineMappingResult, + methodArgs, + mafCutoff, + macCutoff, + imissCutoff +) { + st <- studyCol[i] + ctx <- contextCol[i] + tr <- traitCol[i] cacheHits <- .twasResolveCacheHits( - p$twasWeights, + twasWeights, st, ctx, tr, - p$univariateTokens + univariateTokens ) cachedRows <- imap( cacheHits, @@ -1770,17 +2158,17 @@ setMethod( ctx = ctx, tr = tr ) - toFit <- setdiff(p$univariateTokens, names(cacheHits)) + toFit <- setdiff(univariateTokens, names(cacheHits)) if (length(toFit) == 0L) { return(unname(cachedRows)) } fitCtx <- .twasQssUnivariateFitCtx( - p$data, + data, st, ctx, tr, - p$ldSketch, - cutoffs = .panelCutoffs(p) + ldSketch, + cutoffs = .panelCutoffs(mafCutoff, macCutoff, imissCutoff) ) fitted <- compact(map( toFit, @@ -1789,7 +2177,9 @@ setMethod( ctx = ctx, tr = tr, fitCtx = fitCtx, - p = p + methodArgs = methodArgs, + fineMappingResult = fineMappingResult, + dataType = dataType )) c(unname(cachedRows), fitted) } @@ -1865,15 +2255,24 @@ setMethod( # Fit one univariate method for one entry -> a row record, or NULL on skip. # @noRd -.twasQssUnivariateFitOne <- function(tk, st, ctx, tr, fitCtx, p) { +.twasQssUnivariateFitOne <- function( + tk, + st, + ctx, + tr, + fitCtx, + methodArgs, + fineMappingResult, + dataType +) { spec <- .twasResolveWeightFn(tk) - userArgs <- .twasUserArgs(p$methodArgs, tk) + userArgs <- .twasUserArgs(methodArgs, tk) # When the token is a fine-mapping method, pass the precomputed fit into the # *Rss weight function via its dedicated *Fit arg. The gate above ensures # fineMappingResult is non-NULL here. if (!is.null(spec$adapter)) { fit <- .twasFineMappingFitFor( - p$fineMappingResult, + fineMappingResult, study = st, context = ctx, trait = tr, @@ -1908,7 +2307,7 @@ setMethod( fits = fitAttr, cvResult = NULL, standardized = TRUE, - dataType = p$dataType + dataType = dataType ) ) } @@ -1935,50 +2334,99 @@ setMethod( # ---- Multivariate dispatch: per (study, trait), all selected contexts ------ -.twasQssMultivariateRows <- function(p) { - if (length(p$multivariateTokens) == 0L) { +.twasQssMultivariateRows <- function( + selRows, + multivariateTokens, + studyCol, + contextCol, + traitCol, + data, + ldSketch, + methodArgs, + retainFitDetail, + fineMappingResult, + dataType, + mafCutoff, + macCutoff, + imissCutoff +) { + if (length(multivariateTokens) == 0L) { return(list()) } groupKey <- str_c( - p$studyCol[p$selRows], - p$traitCol[p$selRows], + studyCol[selRows], + traitCol[selRows], sep = "||" ) - groups <- split(p$selRows, groupKey) - list_flatten(map(groups, .twasQssMultivariateGroupRows, p = p)) + groups <- split(selRows, groupKey) + list_flatten(map( + groups, + .twasQssMultivariateGroupRows, + multivariateTokens = multivariateTokens, + studyCol = studyCol, + contextCol = contextCol, + traitCol = traitCol, + data = data, + ldSketch = ldSketch, + methodArgs = methodArgs, + retainFitDetail = retainFitDetail, + fineMappingResult = fineMappingResult, + dataType = dataType, + mafCutoff = mafCutoff, + macCutoff = macCutoff, + imissCutoff = imissCutoff + )) } # Multivariate rows for one (study, trait) group across its contexts. # @noRd -.twasQssMultivariateGroupRows <- function(gIdx, p) { +.twasQssMultivariateGroupRows <- function( + gIdx, + multivariateTokens, + studyCol, + contextCol, + traitCol, + data, + ldSketch, + methodArgs, + retainFitDetail, + fineMappingResult, + dataType, + mafCutoff, + macCutoff, + imissCutoff +) { if (length(gIdx) < 2L) { return(list()) } - st <- p$studyCol[gIdx[[1L]]] - tr <- p$traitCol[gIdx[[1L]]] - ctxNames <- p$contextCol[gIdx] + st <- studyCol[gIdx[[1L]]] + tr <- traitCol[gIdx[[1L]]] + ctxNames <- contextCol[gIdx] mvStat <- .twasQssMultivariateStat( - p$data, + data, st, tr, ctxNames, - ldSketch = p$ldSketch, - cutoffs = .panelCutoffs(p) + ldSketch = ldSketch, + cutoffs = .panelCutoffs(mafCutoff, macCutoff, imissCutoff) ) ldMat <- .ldFromSketch( - p$ldSketch, + ldSketch, mvStat$variantIds, label = "twasWeightsPipeline" ) list_flatten(map( - p$multivariateTokens, + multivariateTokens, .twasQssMultivariateFitOne, st = st, tr = tr, ctxNames = ctxNames, mvStat = mvStat, ldMat = ldMat, - p = p + methodArgs = methodArgs, + retainFitDetail = retainFitDetail, + fineMappingResult = fineMappingResult, + dataType = dataType )) } @@ -2070,17 +2518,36 @@ setMethod( # Fit one multivariate method for a group -> one row record per context (empty # list on skip). # @noRd -.twasQssMultivariateFitOne <- function(tk, st, tr, ctxNames, mvStat, ldMat, p) { +.twasQssMultivariateFitOne <- function( + tk, + st, + tr, + ctxNames, + mvStat, + ldMat, + methodArgs, + retainFitDetail, + fineMappingResult, + dataType +) { spec <- .twasResolveWeightFn(tk) userArgs <- .twasMrmashRetainDefaults( - .twasUserArgs(p$methodArgs, tk), + .twasUserArgs(methodArgs, tk), spec$adapter, tk, - p$retainFitDetail + retainFitDetail ) # mvsusie is fine-mapping; thread its pre-fit through (mr.mash is not). if (!is.null(spec$adapter)) { - userArgs <- .twasMvThreadFit(spec, userArgs, tk, st, tr, ctxNames, p) + userArgs <- .twasMvThreadFit( + spec, + userArgs, + tk, + st, + tr, + ctxNames, + fineMappingResult + ) if (is.null(userArgs)) { return(list()) } @@ -2108,16 +2575,24 @@ setMethod( st, tr, tk, - p$dataType + dataType ) } # Thread the precomputed fine-mapping fit into a multivariate method's args; # returns NULL (signalling skip) when the fit is absent. # @noRd -.twasMvThreadFit <- function(spec, userArgs, tk, st, tr, ctxNames, p) { +.twasMvThreadFit <- function( + spec, + userArgs, + tk, + st, + tr, + ctxNames, + fineMappingResult +) { fit <- .twasFineMappingFitFor( - p$fineMappingResult, + fineMappingResult, study = st, context = ctxNames[[1L]], trait = tr, @@ -2181,8 +2656,12 @@ setMethod( # Combine the per-tuple result with any joint result (error if both empty). # @noRd -.twasQssAssemble <- function(rows, jointResult, p) { - perTupleResult <- .twasRowsToWeights(rows, p$ldSketch) +.twasQssAssemble <- function( + rows, + jointResult, + ldSketch +) { + perTupleResult <- .twasRowsToWeights(rows, ldSketch) if (is.null(jointResult)) { if (is.null(perTupleResult)) { msg <- glue( @@ -2196,7 +2675,7 @@ setMethod( if (is.null(perTupleResult)) { return(jointResult) } - .rbindTwasWeights(perTupleResult, jointResult, ldSketch = p$ldSketch) + .rbindTwasWeights(perTupleResult, jointResult, ldSketch = ldSketch) } @@ -2283,9 +2762,24 @@ setMethod( ) { naAction <- arg_match(naAction) retainFitDetail <- arg_match(retainFitDetail) - p <- as.list(environment()) - p$dots <- list(...) - .twasPipelineMultiStudy(p) + .twasPipelineMultiStudy( + data = data, + region = region, + cisWindow = cisWindow, + jointRegions = jointRegions, + jointSpecification = jointSpecification, + methods = methods, + fineMappingResult = fineMappingResult, + contexts = contexts, + traitId = traitId, + verbose = verbose, + retainFit = retainFit, + retainFitDetail = retainFitDetail, + seed = seed, + twasWeights = twasWeights, + naAction = naAction, + dots = list(...) + ) } ) @@ -2322,54 +2816,110 @@ setMethod( (is.character(methods) || is.list(methods)) && length(methods) == 0L } -.twasPipelineMultiStudy <- function(p) { - if (!is.null(p$region) && !is.null(p$cisWindow)) { +.twasPipelineMultiStudy <- function( + data, + region, + cisWindow, + jointRegions, + jointSpecification, + methods, + fineMappingResult, + contexts, + traitId, + verbose, + retainFit, + retainFitDetail, + seed, + twasWeights, + naAction, + dots +) { + if (!is.null(region) && !is.null(cisWindow)) { msg <- glue( "twasWeightsPipeline(MultiStudyQtlDataset): specify either ", "`region` or `cisWindow`, not both." ) abort(msg) } - xRegions <- .makeXRegions(p$region, p$jointRegions) - parsedJointSpec <- parseJointSpecification(p$jointSpecification, p$data) + xRegions <- .makeXRegions(region, jointRegions) + parsedJointSpec <- parseJointSpecification(jointSpecification, data) # Gate fine-mapping methods early so the recursion into the embedded # QtlDataset / QtlSumStats components doesn't re-run fine-mapping. .twasCheckFineMappingMethods( - .twasMethodTokensFromArg(p$methods), - p$fineMappingResult, + .twasMethodTokensFromArg(methods), + fineMappingResult, "MultiStudyQtlDataset" ) - joint <- .twasMsJointPhase(p, parsedJointSpec, xRegions) + joint <- .twasMsJointPhase( + parsedJointSpec, + xRegions, + data = data, + methods = methods, + contexts = contexts, + traitId = traitId, + cisWindow = cisWindow, + verbose = verbose, + retainFit = retainFit, + retainFitDetail = retainFitDetail, + seed = seed + ) if (joint$done) { return(joint$result) } - .twasMsDriver(p, joint$result, joint$methods) + .twasMsDriver( + data = data, + contexts = contexts, + traitId = traitId, + cisWindow = cisWindow, + region = region, + jointRegions = jointRegions, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + naAction = naAction, + verbose = verbose, + seed = seed, + dots = dots, + joint$result, + joint$methods + ) } # Joint-specification dispatch for mrmash. Returns list(done, result, methods) # where `done` requests an early return with `result` and `methods` is the # mrmash-stripped set for the per-component recursion. # @noRd -.twasMsJointPhase <- function(p, parsedJointSpec, xRegions) { +.twasMsJointPhase <- function( + parsedJointSpec, + xRegions, + data, + methods, + contexts, + traitId, + cisWindow, + verbose, + retainFit, + retainFitDetail, + seed +) { if (length(parsedJointSpec) == 0L) { - return(list(done = FALSE, result = NULL, methods = p$methods)) + return(list(done = FALSE, result = NULL, methods = methods)) } - jointMethods <- intersect(.twasMethodTokensFromArg(p$methods), "mrmash") + jointMethods <- intersect(.twasMethodTokensFromArg(methods), "mrmash") jointResult <- .twasDispatchJointSpecsMultiStudy( parsedJointSpec, - p$data, + data, jointMethods, - p$contexts, - p$traitId, - p$cisWindow, + contexts, + traitId, + cisWindow, NULL, - p$verbose, + verbose, xRegions = xRegions, - retainFit = p$retainFit, - retainFitDetail = p$retainFitDetail, - seed = p$seed + retainFit = retainFit, + retainFitDetail = retainFitDetail, + seed = seed ) - methods <- .twasMsStripMrmash(p$methods) + methods <- .twasMsStripMrmash(methods) if (.twasMethodsEmpty(methods)) { if (is.null(jointResult)) { msg <- glue( @@ -2385,23 +2935,38 @@ setMethod( # Run the per-study / per-component recursion via the shared multi-study driver. # @noRd -.twasMsDriver <- function(p, jointResult, methods) { +.twasMsDriver <- function( + data, + contexts, + traitId, + cisWindow, + region, + jointRegions, + fineMappingResult, + twasWeights, + naAction, + verbose, + seed, + dots, + jointResult, + methods +) { cfg <- list( methods = methods, - contexts = p$contexts, - traitId = p$traitId, - region = p$region, - cisWindow = p$cisWindow, - jointRegions = p$jointRegions, - fineMappingResult = p$fineMappingResult, - twasWeights = p$twasWeights, - naAction = p$naAction, - verbose = p$verbose, - seed = p$seed, - dotArgs = p$dots + contexts = contexts, + traitId = traitId, + region = region, + cisWindow = cisWindow, + jointRegions = jointRegions, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + naAction = naAction, + verbose = verbose, + seed = seed, + dotArgs = dots ) .multiStudyPipelineDriver( - p$data, + data, jointResult, .twasPerStudy, .twasSumStats, @@ -2442,11 +3007,7 @@ setMethod("twasWeightsPipeline", "ANY", function(data, ...) { # @noRd .solveEnsembleQuadprog <- function(Pvalid, yObs, Kvalid) { if (!requireNamespace("quadprog", quietly = TRUE)) { - msg <- glue( - "Package 'quadprog' is required for solver='quadprog'. ", - "Install with: install.packages('quadprog')" - ) - abort(msg) + abort("Package 'quadprog' is required for solver='quadprog'.") } Dmat <- crossprod(Pvalid) @@ -2459,15 +3020,14 @@ setMethod("twasWeightsPipeline", "ANY", function(data, ...) { Amat <- cbind(rep(1, Kvalid), diag(Kvalid)) bvec <- c(1, rep(0, Kvalid)) - qpSol <- tryCatch( + qpSol <- try_fetch( solve.QP(Dmat = Dmat, dvec = dvec, Amat = Amat, bvec = bvec, meq = 1), - error = function(e) { - eMsg <- conditionMessage(e) + error = function(cnd) { msg <- glue( - "QP solver failed: {eMsg}. Falling back to equal weights ", - "among valid methods." + "QP solver failed. Falling back to equal weights among ", + "valid methods." ) - warn(msg) + warn(msg, parent = cnd) NULL } ) @@ -2495,21 +3055,14 @@ setMethod("twasWeightsPipeline", "ANY", function(data, ...) { # @noRd .solveEnsembleNnls <- function(Pvalid, yObs, Kvalid) { if (!requireNamespace("nnls", quietly = TRUE)) { - msg <- glue( - "Package 'nnls' is required for solver='nnls'. ", - "Install with: install.packages('nnls')" - ) - abort(msg) + abort("Package 'nnls' is required for solver='nnls'.") } - fit <- tryCatch( + fit <- try_fetch( nnls::nnls(Pvalid, yObs), - error = function(e) { - eMsg <- conditionMessage(e) - msg <- glue( - "NNLS solver failed: {eMsg}. Falling back to equal weights." - ) - warn(msg) + error = function(cnd) { + msg <- "NNLS solver failed. Falling back to equal weights." + warn(msg, parent = cnd) NULL } ) @@ -2532,12 +3085,24 @@ setMethod("twasWeightsPipeline", "ANY", function(data, ...) { # Ensemble stacking objective (sum of squared residuals). `...` absorbs the # gradient's extra optim args (PtP, Pty). # @noRd -.ensembleObj <- function(z, Pvalid, yObs, ...) sum((yObs - Pvalid %*% z)^2) +.ensembleObj <- function(Pvalid, yObs) { + # Captured by name, and forced here so the closure holds values rather + # than promises into a frame that has already returned. + force(Pvalid) + force(yObs) + function(z) sum((yObs - Pvalid %*% z)^2) +} -# Gradient of the ensemble stacking objective. `...` absorbs the objective's -# extra optim args (Pvalid, yObs). +# Gradient of the ensemble stacking objective, same construction. Building +# both as closures of exactly what each needs is what lets optim() be called +# with no `...`: previously it forwarded the union of both callbacks' +# arguments to both, and each absorbed the other's half in a `...` tail. # @noRd -.ensembleGrad <- function(z, PtP, Pty, ...) as.vector(2 * (PtP %*% z - Pty)) +.ensembleGrad <- function(PtP, Pty) { + force(PtP) + force(Pty) + function(z) as.vector(2 * (PtP %*% z - Pty)) +} # Solve ensemble stacking via L-BFGS-B (box-constrained optimization, then # normalize). Uses base R optim() with analytical gradient. No extra @@ -2551,25 +3116,19 @@ setMethod("twasWeightsPipeline", "ANY", function(data, ...) { PtP <- crossprod(Pvalid) Pty <- as.vector(crossprod(Pvalid, yObs)) - fit <- tryCatch( + fit <- try_fetch( optim( par = rep(1 / Kvalid, Kvalid), - fn = .ensembleObj, - gr = .ensembleGrad, - Pvalid = Pvalid, - yObs = yObs, - PtP = PtP, - Pty = Pty, + fn = .ensembleObj(Pvalid, yObs), + gr = .ensembleGrad(PtP, Pty), method = "L-BFGS-B", lower = rep(0, Kvalid) ), - error = function(e) { - eMsg <- conditionMessage(e) + error = function(cnd) { msg <- glue( - "L-BFGS-B solver failed: {eMsg}. Falling back to equal ", - "weights." + "L-BFGS-B solver failed. Falling back to equal weights." ) - warn(msg) + warn(msg, parent = cnd) NULL } ) @@ -2602,14 +3161,10 @@ setMethod("twasWeightsPipeline", "ANY", function(data, ...) { # @noRd .solveEnsembleGlmnet <- function(Pvalid, yObs, Kvalid, alpha = 1) { if (!requireNamespace("glmnet", quietly = TRUE)) { - msg <- glue( - "Package 'glmnet' is required for solver='glmnet'. ", - "Install with: install.packages('glmnet')" - ) - abort(msg) + abort("Package 'glmnet' is required for solver='glmnet'.") } - fit <- tryCatch( + fit <- try_fetch( glmnet::cv.glmnet( x = Pvalid, y = yObs, @@ -2617,13 +3172,11 @@ setMethod("twasWeightsPipeline", "ANY", function(data, ...) { alpha = alpha, intercept = FALSE ), - error = function(e) { - eMsg <- conditionMessage(e) + error = function(cnd) { msg <- glue( - "glmnet solver failed: {eMsg}. Falling back to equal ", - "weights." + "glmnet solver failed. Falling back to equal weights." ) - warn(msg) + warn(msg, parent = cnd) NULL } ) @@ -2787,6 +3340,7 @@ ensembleWeights <- function( # Validate the required scalar / presence constraints on the raw inputs. # @noRd +#' @importFrom checkmate assertCount .ensembleValidateArgs <- function(cvResults, Y, contextIndex) { if (is.null(cvResults)) { abort("'cvResults' is required.") @@ -2794,13 +3348,7 @@ ensembleWeights <- function( if (is.null(Y)) { abort("'Y' is required.") } - if ( - !is.numeric(contextIndex) || - length(contextIndex) != 1 || - contextIndex < 1 - ) { - abort("'contextIndex' must be a positive integer scalar.") - } + assertCount(contextIndex, positive = TRUE) invisible(NULL) } @@ -3292,16 +3840,40 @@ ensembleWeights <- function( } } -# One region's univariate joint-cell fit (region `bi` of p$xRegions). +# One region's univariate joint-cell fit (region `bi` of `xRegions`). # @noRd -.twasQdsUnivRegion <- function(bi, univCell, p, scope) { +.twasQdsUnivRegion <- function( + bi, + univCell, + scope, + marker, + data, + xRegions, + norm, + fineMappingResult, + twasWeights, + dataDrivenPriorMatricesCv, + cisWindow, + naAction, + verbose +) { .runJointCell( univCell, - p$marker, - p$data, + marker, + data, scope, - p$norm$tokens, - args = .twasQdsUnivArgs(p, bi) + norm$tokens, + args = .twasQdsUnivArgs( + bi, + xRegions = xRegions, + norm = norm, + fineMappingResult = fineMappingResult, + twasWeights = twasWeights, + dataDrivenPriorMatricesCv = dataDrivenPriorMatricesCv, + cisWindow = cisWindow, + naAction = naAction, + verbose = verbose + ) ) } diff --git a/R/twasWeightsRow.R b/R/twasWeightsRow.R index c96120d28..9f4386a54 100644 --- a/R/twasWeightsRow.R +++ b/R/twasWeightsRow.R @@ -41,30 +41,25 @@ setClass( ) ) +#' @importFrom checkmate makeAssertCollection assert assertFlag +#' @importFrom checkmate checkNumeric checkMatrix methods::setValidity("TwasWeightsRow", function(object) { - errors <- character(0) + coll <- makeAssertCollection() w <- object@weights n <- length(object@variants) if (!is.null(w)) { # A matrix carries one ROW per variant (columns are conditions), so # the two shapes need separate checks -- validating only the vector # case would let a mis-sized matrix through. - if (is.null(dim(w)) && length(w) != n) { - errors <- c( - errors, - "length(weights) must equal length(variantIds)" - ) - } else if (!is.null(dim(w)) && nrow(w) != n) { - errors <- c( - errors, - "nrow(weights) must equal length(variantIds)" - ) - } - } - if (length(object@standardized) != 1L || is.na(object@standardized)) { - errors <- c(errors, "'standardized' must be a single logical value") + assert( + checkNumeric(w, len = n), + checkMatrix(w, nrows = n), + .var.name = "weights", + add = coll + ) } - if (length(errors) == 0L) TRUE else errors + assertFlag(object@standardized, .var.name = "standardized", add = coll) + coll$getMessages() }) #' @title Build One TWAS-Weight Row diff --git a/R/variantId.R b/R/variantId.R index af71ee717..e537e21ae 100644 --- a/R/variantId.R +++ b/R/variantId.R @@ -227,8 +227,15 @@ detectVariantConvention <- function(ids) { #' stored as \code{attr(result, "convention")}. #' @examples #' parseVariantId(c("chr1:100:A:G", "chr2:200:T:C")) +#' @importFrom checkmate assert checkCharacter checkDataFrame #' @export parseVariantId <- function(ids) { + # `ids` is documented as a character vector OR a data.frame. + assert( + checkCharacter(ids), + checkDataFrame(ids), + .var.name = "ids" + ) if (is.data.frame(ids)) { return(.parseVariantIdDf(ids)) } @@ -355,8 +362,12 @@ formatVariantId <- function( #' rsIDs) are returned unchanged. #' @examples #' normalizeVariantId(c("1:100:A:G", "2:200:T:C")) +#' @importFrom checkmate assertCharacter assertFlag assertList #' @export normalizeVariantId <- function(ids, chrPrefix = TRUE, convention = NULL) { + assertCharacter(ids) + assertFlag(chrPrefix) + assertList(convention, null.ok = TRUE) parsed <- parseVariantId(ids) out <- as.character(ids) # Only re-format ids that parsed into a chrom + pos; leave unparseable ids @@ -468,8 +479,7 @@ harmonizeAlleles <- function( removeIndels = FALSE, removeStrandAmbiguous = TRUE, removeDups = FALSE, - colToComplement = character(), - ... + colToComplement = character() ) { coerced <- .harmonizeCoerceInputs(targetData, refVariants) targetData <- coerced$targetData @@ -1179,8 +1189,13 @@ parseRegion <- function(region) { #' \code{colnames}: a normalized character chromosome plus integer start/end. #' @examples #' regionToDf(c("1_100_200", "2_300_400")) +#' @importFrom checkmate assertCharacter #' @export regionToDf <- function(ldRegionId, colnames = c("chrom", "start", "end")) { + # @param says "A string", but the function is vectorised and callers pass + # a character vector -- assert what it actually accepts, not the prose. + assertCharacter(ldRegionId, any.missing = FALSE) + assertCharacter(colnames, len = 3L, any.missing = FALSE) parts <- str_split(ldRegionId, "[_:-]", simplify = TRUE) regionOfInterest <- as_tibble(parts, .name_repair = "minimal") colnames(regionOfInterest) <- colnames diff --git a/R/vcfWriter.R b/R/vcfWriter.R index 31441f70d..6da639f6a 100644 --- a/R/vcfWriter.R +++ b/R/vcfWriter.R @@ -324,13 +324,13 @@ setMethod( # marginal ES=beta / SE / LP / AF on top. Returns list(base, m, hasPost). # @noRd .vcfResolveBody <- function(entry, sn) { - post <- tryCatch( + post <- try_fetch( as_tibble(getTopLoci(entry, signalCutoff = 0)), - error = function(e) NULL + error = function(cnd) NULL ) - marg <- tryCatch( + marg <- try_fetch( as_tibble(getMarginalEffects(entry)), - error = function(e) NULL + error = function(cnd) NULL ) hasPost <- !is.null(post) && nrow(post) > 0L hasMarg <- !is.null(marg) && nrow(marg) > 0L @@ -558,6 +558,7 @@ setMethod( # BCF path: write a temporary bgzipped VCF, then convert to BCF via asBcf. # @noRd +#' @importFrom rlang try_fetch .vcfWriteBcf <- function(vcf, outputPath, chrom) { tmpVcfStem <- tempfile(fileext = ".vcf") tmpVcfBgz <- str_c(tmpVcfStem, ".bgz") @@ -570,20 +571,20 @@ setMethod( # Rsamtools disabled asBcf() (>= 2.26 raises "temporarily disabled"), so # the bare upstream error is translated into something actionable rather # than surfacing as an opaque failure from a documented output format. - tryCatch( + try_fetch( asBcf( tmpVcfBgz, dictionary = unique(chrom), destination = str_remove(outputPath, "\\.bcf$") ), - error = function(e) { + error = function(cnd) { msg <- glue( "writeSumStatsVcf: BCF output needs a working ", "Rsamtools::asBcf(), which the installed Rsamtools does not ", - "provide (\"{conditionMessage(e)}\"). Write a bgzipped VCF ", - "instead by giving the output path a .vcf.bgz extension." + "provide. Write a bgzipped VCF instead by giving the output ", + "path a .vcf.bgz extension." ) - abort(msg) + abort(msg, parent = cnd) } ) } diff --git a/data/ctwasEstExample.rda b/data/ctwasEstExample.rda index 656d1143dcd96cd225d2e864ba897a998bedb87b..58021fa7df8d68f753eb128edaaf0037dda7f814 100644 GIT binary patch delta 257502 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]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 z zz3y!Sp2%33PWBZXg5N%sfJ=>ThRvi`KP~Dt+LyQMP?S_9UjETn`O%Iu1NMTnmKdrY$KcXHZ^Aeho9b8yT!Dt6Giw!mJ zm0dEq`MG5GRs~dgN)!d_4j(WaTYud{`y~?GR7%j9K$Rs{*bqc((MpaR+OXvf+u^RW z=FaAk*CP*mf*dbFx@@98$5<{m`aZI5m}zRP)f}_=j%puELs<%UsFF;WUvLeLMCi=K zT0`0_E@)yLJYIm=Vh#MRvaWYDq!<|5&1_!{XLPr>L>ZD&+C=$!sFW|1_`y=C$ zcqC$^4}!E}6)TIR1WM>^Dx&bU6N52Ll*XLYTfABM~7cgll#YM?O#x zs%u`Ice>LNZ63m!Vo(1%Q-7AlUKZv+QA_^Z)bV{pS>Loq!OYX)_{^r%3NlwMJ)61( zrkTU87)%G6eNX<}E&*N*DAEqFmDe6qI|4sry#6c2LupK)FJ=PPW(PlpfXeDq9w4xx zR9AaK)wd=s3xA?+>!3lQg4H=z(8M={qZ%1bmTFK%*$#-q_G(Z~(|<(b02^^#$;1A$ zss<#ZC6rvC@Xo{9-=|4zT88RD({`cG$8ZPVXTJ4rI;f@&`y9cAGr3*8g8nc4#Usf- zv-b0g?!vtm>SjEB0COWF{X=?Pswudx4%_wFa^=`#=bZsFDYEF$9h<1k7=qUq+Jkx- z9jLOu?#UvbZ(y;>GJpBq0LR144)zMKWo1fSU@DLj3gh}wbd$d za{qO@BpRA3YJZuGf?EOU{@?Mv|3g2pna zgnr96HW32f&)hNJ?`2{7i~zZ_AAM${2D#^jk*1cG6X`Ysc||>uCYAmk5b?%9#uj44P+M%b5t$(JwBU$0?i0>}E)2|K8ejpD!y3Q-dl7CtI8=|c->u*=%$(@aD#gpkh z4e>RHcPPQLS1MVugYDtYKmTy>?kPRPCl^r|;uB z-Q&utL9S3J2?t}dAGfpg%gQ{X#Z@hn?w>8 z20yN4l87D(DaG48<1#z@(p3 zl>IT^E7*@Qx}mM$wtOAhM+r4Hy2_vS^s#Rwg7&7}#Kkv;mIwim%G>&`of`HQ)0-}% z5)1iWea<9=amQsvVP!>W)9{7^JH*Q)xFWUFAk&Rfd#_{g4Yd>QeX#NdAiRAfiqbP~HOT zh_1>Q3QNSB5fo7i?aA-Cx8XC-R!XdJ?OHrP_s3hNU++i{6tBuQIA4Pj>+{ozEjf+< z{jv`pnjJ5BGkv_YX}d9e`_=%cV^i?Wm+Eu~R@8&+P+sqEx(|rqc>xS2UCB1;nBcxTa(JpHS5!<|y z%lcZkp;pg239Nway8mXfK4Fn44Cx2?0*ocMpyCQ*Go%LupePTA3V%nGb(*1YRCyDQ zW+SNHP=<)8toL}jLAQWGhuV(9^1B&UPf}3CKGGGWF%!nugp5O5HO;1+tYmwRZe*V- zQXhPc$>V(TDd-!KV!RIzIdPyGS>!j>Tv+cDQ>q+Bn&^@qA>+cQ!w=FWh$yJR9BNxJ zsc8154Jw$Hb|_SIQ-A2*B|1Ffq)7|lqhF$?t2qx3J(@6@w_&Ch(BL)tVn9he>*lJO zFU6WOF>3Nzg7oP6Fl&uw+QDAuD$iMhTT4)TOL<60YJo}N^{w%|Yh=K5Bd|s@Y1pss z---eaZ~^wCUL}ovG4bo(bdjEB@dA=cxw0+ud_JPh_ud~sSbrM>C-2Xn7KXG~9oy1N z-zibbJk=it_MJ=%G-Y4%<~{?F^#@J;pxX_8ClWmAxb|>0a)qG{muMv^=`s(A?5(sd zEBFWw1^UXwbrkZQEpE8kvi#22_0Wb#NH40wjbE;*Y-ScE<5Z?W{%vnzCMmGX+Y0N2 z+E4d(7Fpn&>wnd~Gb2t+0x~(ATZ*i%fWifea(DXojY2*H#L~e694iomkyKTT005!_ z1mDnP{h#yBLFShwFlA-q2FT($yQW;m*Bz;Hzf$xt4sEAI7m^;cP1+1Ig3#~#D;kTc zmrr0cwg;&w&ScGRDwfenz=p@^h5etgW&=Kxw;7ur&wm)=A81)Or!J;oJjH5gRur@B z`R_rPDpRAl^L*l2{Az5p%wGw!xW1esJb{iczcZs|fY^(SgDqNnN z)-!Jgdrx+Mt1o0EJetYWsx-56hys4|!0nxAz-P^DA;-H)1R=53KXn>;sxrCIvo~z7 z33bci=zsDZquR0>=;jd=%8MivbJxY=V;gywEP07QxryM-@GGej>mNcnt%1yE@BjTiD+JC8bhd{{DU$HFmkunkyIFO%l zaewo@eEgUa;2AVN@$Dw>nih+u3*QSHl>EXXYbp~K`H=4C26y7XtdA>9%%s0?q9ndH zkvbT&s4AR8M2Dv4Lj{q^`^MxvO~DE0_gtILHHY$$D9!6cV2~W$ohJSC0|{pL07M1; z=d8)1Hwm-OzHuROJjA^8%nq<-Nr5H(AbF(DXNG)1Fxqe6;4ye0~xN*dz3M@ZslIc(kUhbsM&g{-W8s(+wK z%pX6+;vVk zZ$zyVK}IH;Rqc>Xjr!ajt;|1nbv@VY>C1gZd7Hh|03tVKi`C2P#>#e+_zkr^0_=ER!5uLF+5sDO$twqe3x`SM6P`EEyu<9V{WxJ zd1F0Jb7$BG@<1q7WSas>E?Rp$X5qS4jxQ}8L3+rt#9y>(kzX$8(qvae)boR{%NrMY z7%M-OL(5^jbB`)y!H3RRJb!8UFfRFPw?cjdEAuBoA*XL;K@fX7v`olMg|`h;#{z`E zywUnkS>Bm^47ggNVuN2!yWrAF*H-d|aM*?9Zk^0{uTO&Qw<7l|c2BjW!W_)f@x3xw z!MMA%q0=KdKVTYplrIEWK{Qh1#IS0#Qy;HfCt$c@`tXyzAofr07=I3=KdaQxG$9%T zDSq~gs;%VbM&LjC(X-+C3_zf2<5fK7O-}Pymx&H*{UQQS%Cur0WZ@8FZQr^r@RkN1 z;8Dl<1!ZW!R#+7x@ zXd1f7Pf$aAf*!3HMMb>Z5 zSmqtZ;yK#d%hpb%0~@Wt7cm>B=AF+HrOw4;QGlEXOHpFH zM1rp*8-Audl_PR6M*CN8DeNNQPa--`@-C_$M8tT|Dx-F;_J4Zd9C1i#LShyGoQobW z%N!F0zHrr{Hbrua3vHd0oj~zDzzUTHTz*tH>6R}A`-EaNd}4v>9)#W8BNMP@mb4Q# zII4xMC{S}=&)aciF*ucgQ`@sI5RR zL*tJMklS-=uOp2w(cxzdQvS=j0v9eayyfboo`8KNeT7!?xz0Fio!qP|vL1rzCSMSp0h*)QYCW0KNK3N;=;SfZ&C zhLt@F^N0o$iwu|$27oUYg$S8?hf>5gjzn7+ut@9ogCgUzV06gH7 z5GK#CW`C7ZLP;tgUGI>3ynN!*(n+nQppn+UWnJ|`_uk4MhtCwLkV~~h>yIa{{iy6w zSYGdxsL(u9oY@@lVChT(aj+tMTaLIAzGqAePydn@77X5zPtCsn^uVT=MId=1lf=!} z38vC1zl@HmTn>*PMTZ@;x2a`YFk!*9?6FY-m48I0SJDK+2knwTj~qgs@@tH!1#as5 zTl)Cek$Go2*p`7zjp#UAdNKGpdb$}_Yy)3=EQ)MBXbGj{?Si3V zo$s_|m2md4&ho*6SE43S>bdH}S6#T0i%Sdq_J)jai8gc-{)4`_5L}a^hgg z^nYwyiKuJVw)>*G6C3dOUT3%tB&eA}3>?GE{mGSbpRO+H)epF*qv@VL6jHw8OG~CuOR+51hUsSklT~=EOx}*J^SpQaULP z(r8`T8NfCruCD_}Zfz`BSdvap@C#dX9DjIe#=@k^m*CV@fV0XYa{U|@33Aevcmlyf zU-=Cie)Z6%6imz%awKSeCE^s&_IGC92}UcuIDG}VmR+C>2#hW6VX@}HaI&@6dE{yA(K1Vg@5lu zjz52HkX|6MgTN$waM;Hx-@ua#WY6uRrW#9xFdrrW@exU8a3<1JL3T-x8Kfug|!2gBBTV&u8T{t^-V z?lItOqIYgrVr6=JOAwF_y^syZ@J!j2W+oJy40RfoXG;!7vh~rl{mxr(zh*-9PNKq- zIG2!v$zW4(aLE9Wal-0b{pWRd+3*TStf%;1zOmT1&1`a()qlf#$ugIS z`QNQH#3W@$%kaLCo`lUS?9y)1N|IZo^#(+j!zT`H`e!I-M>#g(_otDdA;)EZIZWyJ zNDbz_R^U@VT}z$WB0NPKJK$0C-vRFJM~04Ow~$HRFES-#EPtTp=nHkNa77Li%Yy2e%yDbw${5Rm&0klu@a9@_oXtb~ z4G&W-0x&?;`haWdQYLpbk!Y>as}Jipdni%4zG2pa1bGCq1+?5^wIZM+e1Z47bU`)> z=yJ$~zit+ZQ!!wDNM?fB0W9q8d&Q(MugpiBEiF4Ckt$>9&iEsGNqOh_PQ=Rr6tn?+*JZBsNe+9@iBMR+XU5e1+<-$QylI94g`>i&Tb9`|JUcb|$~@ zt)MS8x4GRGcEA5~j(_>IrV_s8t(>~1EK(7z!gzCD*;*v3jmBdlaEz@x6s<#KG0aRO z@DF$s`O27U$wGb))UMRKE*yO@$Oj_l9DKgAjLemxsK`zNvtyV-W7X(>EyNgkxcAAl zm*2CAHkECRSt?9BzXO^sSBEn_zaUZ#WPp7khNhsJ=6A++#(yx5)>*#uvUf}I=^^gs z^f*A{Sw=wO%e3r$6vEW#k}tPf0p@|yUp7mvIcHBG5Llp#rgfc30XIDX*(n&w96TfS-N>~%o2 z5G|!_vmcRcOd2ub=95rx;~QDqDe{+<>VSGxWcGoESl_C#QdYvw7v`HwoS6?U=15Xl zyu+zvn+!zZ$l)oYx?pNXOGhM;w!BTYe((LW+_vIExqs8+Cj+~6a5EE)4R~w=l?WfS zLiqBfssO5=`C9cO^}-22PU1qR@`Wgzxm}u#XL$wC`!RHpGh9#u*ps^B1C1cC+Q+GZ zb$YL*mg(%ro8tKuYlKb!ML^^{l~~Kk7((@)m#JOPUk20Xer(F_+UGAy?)s4&^qU12BUrSSu+~ zJPKd_&&j)kre{f|9>D}VS^dV>LB_8WE95qPIe(*}-BKR)49ZOyS=(OD(BdnM`j0^D z90-i%8_{_VZORmpNlDin^r|ki>TtGcKyN^Llh!yts8xm_AC%bMeJs)~ zEr0P%N|o#SBHy@ch5EAZ9uZ!+147BjK;D|mH{sEH7sY0)iYm#r-#l&t>)Bg6Z`nH+If`}Tt*tK$(M(FazEQ>xgra=Q0N`je2 zvD=Phpgt$bHSx;kjIcYnJ3(Wwo6egkW4@pc>-QTW|9E<{3~5Xh+;ZDi+JWCm6Mw53 zf^s>T>V9wAea2NV3#SlSxfL_@{QColIDUvtPd4w1!?*#OO2q7>!n+ck--Bqec|!K; zJkSx#KCyxtU#46(O#2A0(NS#O=wo5dmb1tU8N9pi~5t;b}tP^AZ@ zU|tCcA%LOBT1ie`TOr%lz`a5K#OnkNAM}gY$RC3+`D4Xb{UpvwKSLPKIlT>yM-;Vr zc0%C@7Vn!udoE!rPq!Vz_hKbSWi#E|NwXY8Ik*}7>HOzk|D=><4CbcC@Z}Ew5rx<4 zBxIhKH1Rf)hL6Q$*8MpyGJiD39tsb8lI6;VhO%@@9^=_R$1tcrpnf#n(vEI{I!u{% zYXBhyG+f5YSPA@jaGp z0XZEblJi0!W68y{Un;YO0>!*)Wf!^AM$Y;pl^-psB~`hoKUx#~a$hc#z!TD|*_?Pq z+a$dQeF5p5ytuxl6W)U14ysw8=(JTImJfP5;KoZcClhSkP&(XQ1$c0I_sO&CF>38J zcYBIJjzLmjfG4VgY=26k&pI6%daKV2B?qchKIB*r^>JU$VGCaK+;Aa80*MntO=hiN z+J+rr*?F6F*u2Y<0zReRLnxG={iR7Gu@6S_DQ8JRizcDc;Ez<*aYk+1*Uzr2RlBu| z()^IUEBd4O-kg{cGpJ524ak?k@VAg8HgN+XIUObO>XK%U4u4&080AlfgSph-ie!L^ zWE?|b@zjUypODe8a2##i4ye>BHLBuqHjwdD)hWk*P88ndFur^2^(+cSs2(fuJJBUp z-vgc4GeHY0<$y|ay5=C}rW+44Q^uSed#PASIcKsM5>c(U4zt&n^0!E28NS0sVQlTi zVI$w%4s~H@Vt<|>ZtKk?A+l&zBCL>0d{($FQ5ipbI1K8G%k|{O)aRvI(3Dib@yFMW z6xTNK8v}ffyWfh1y78TMnT%Efgca6#aO0hj`6dL_zAS=D!5}qfwPu1dSee#nRKfxE zlUxid5ABHBg*~6xEjumi?{-$Y18qBqPRb9t#gy5vgMVVZqAJ`+EB1=$&|CUf30=bs z>SE60V&41ojo}qkj|2NBla(B>FK0D2pPWJ-{v(x&uu>2uVX*8}$He2H|}6(!H(q zI{mECogj@3H8}~BX(29CfvyN3QMrWu${9!8aesTscetc62S&B3Yw7V*y52Kr)(Iy? z*TV58{0qcYiNB$lASJT%b#z?#n1CAFHYqF2&$}so>?_Onpc2_wXS<2u916h;Uu~#FoH##+R5CvV3D(Zi!YSH zoi4P-vI%aJrow$2#vd4|`KYk{hQU=JA;7Z-?DSBN^o%|-QIh7dA!80KHXbe;*JTku zMHoe$8T%GU%WqNtLsNg(IqfjT3G|#%NPq5kQ4$#QBBsQ8yqF0cvl_nK`xRXvWW?5K)=nivVhUqh#k_6Zl*JI+@xKz(NujvwxN3C{Kt?IcV{M>*Rry= z&wL#ba6{$+@JW1Wb>IjvtB@y7T$h)RGXHpUT^c;2esI$Ts9|G8jVb2*Oopb!M}It! zXLD|ufI(8>@l$OS3I{tu?Fp-O3V1K&yF2fa1|Xdn1S51#OUgl^^&lyPhbRlj(rF@a zgc1#gfJ|4%>!rhFJq1P;>|=ZrAqvVQbAj9Ba9xV(MI&3FKM60?UIXIR4S$x8VQ68w z8KH5NRBig9$R^zV|G19<{&WO>+O&Deu?&DalO;}fr5eenRE;kdW}!XST$dhIlGXZ} zOA7x3iknR<717weT_y)DJz@bsat>fIrOkeDEWlfhne4K{RQa z2;`z;yy*x13RIGLEQ#8$Cx5@sl9%lBK$#gACtUk`;T1Xx$ZyF5k2Z{DAAI!w)|A-Ev6X}V^Oy|N}h}~R$*tp!nv%Q+2?|o5a zymWQuQQSYaBmt4T!~X<=db#-IoO?__27aTG(SuG^HV@eycw{k*=g;5ufIHl#K+WJ; z)oPkG2Y?sD`~AmxYJb-(N5}o1q9uRROL*4-Dvl_4Zcb}w^IGR-+xVhEu$?gC3I=-U z2rbJZCPU*YcIlZpDGF%cf|BR8>~?888P(;3E}JT3L~~H0TW;;J11r1wW(6v#4M}#P zO@(tKdgV4%l=?lAA^E8E$G}}@JZ&aRQ)N`@zZjvUv1g-nXn)T-Hm(j9AX^^x!*u7I zK<2(P<;WmJ+o0wyYlfBC>B$w2s@g%G;Hc5KX7Ky-CVO2KUL#2;J;(neP&-Ip(y;Xo z^s8)N_bZ<#$WSS1i)lUT_-2)vazKp!5!SS1g_8G`J8TXTc_9VHa>0wF2Ks5$-onSxQD8e?gJpNVz_euNJwM^5&om%v-8pojQ+squz#T#1p zeA=t7f(2@ZdW8T0U{9s&A4`c5+s$2=>EsksukvML^nX;(dNd2d*UDJCl>8N+JuykmoyY6?L-=?!7IuU3#cHjvlKMF0{4NeyJ8t96J2#y ztZ9!MQJN|<0Yx-W-b$dbd_d0)m9s)|)P+=gn@&_Z+VAYRc!RijBCDI|6m5w&3^5kP zUzz!`0mFNr8lui8kP>?m>Hy2znDl5}t$+okoqsiW?qH8kW0j&fn_~2CKzsiqh2hr> zf~nC9)NdNFmk{V-N}5>hj^>Q(pACZZo1}M z@W$a=QmmDRgiewz(2YnxF*}@}Gl}o6Gk*-tYXk2diOX{0lJxjxgGc5;S1pyql5ENn zM6ikgf&&Vz5ZT!limeEW692AWMVR2(O@E@CD9T{B)(YA{vgzEMzAMdQ-6ine6cs!oP@k-Yfox9jI?YdjUO5j4&B*Fz_5BhM#NkEa=a`UV+$ zw4zoN@VV)jSx6t{b2P@t?S~;gh^`E$iG`QQz zEybR(47}+;x-{di%(TtCh10YR4y}hY2Iv3Qh#&Pl73a%?55rDE z-nx+E!8HITCTqMs+~S7fJILt{eGB!kNE3rouspatWnp)1Hf!*Rx~8~?SzW-PKk=(b`u>q2Y)041@W#1V5K4g>6CL{VC>5Rk%S!mL;zezja$(CS-FNy zMW<+y+<3)bJm18Gbh`O07H%n-pJPfq-mQBzTMsXiP`RP|D~NQX-m(AV0Amu*(X1$E z6`Qg!4z+-FV_<;!V|8ePJ8I(kVYKUX4#m|=_RkIyHyyKu?g{k~-+wYzRkn^8^%%kZ zr8WLc%43!yzf@4PnYJh63(EOiLCE~=(O~$F;cs$&Y)bydw8c-*=LdE$In}m^F~tVA zl}u#>GyQ%n)h{G_&CKA;9f5@gJGH3GaLxwNuX9M8*hVLD$y=Lr^x4~eC6YzbLduBx zSGuZCpgs+(vhuqtntv1pPxsfH^uG4ov@n`7vt0k0MXuA6Y$24wWK_ z6pI|iZsO%@lAqA!rC^5YBOI2L`1nD!Kah)FjbpjdTPXWuLoELSo8+A?q4nEIw7V#a z;IA6$TaQ@!@KU~e#)VEAxA)Mus~aM>Hz1fjE!^U#?l^q_O@9wcH1FY8u1MieGM8xN zG0;VKZuK;F$c)gwAn-mGWe>pEOg7bZdA=H~DdmY1WV?wYLi{cN3-8$Dt{L>~QG7%6 z)qp;=Z8{%((WL7S+IAcELl2UDvA%gw{1iYp@dv&r**r{-DxpSvVK)ba`2oT+ zUYfDepgUY^(|;j8tz7uXQcQPC$|7gHS>+A0LYD#;)hGFejcWLt9x#LBTAMYB2rH7c zPaU;w7nW?bvf0L#_%k~b#;_|OU4#hJ?!bGc^-3iki(RQ_4+5i-iM^o`CmuX0j40D&MPg%c=pUs zkPI;#U(WIA3V0aZdOYHqAw!*cFTi?FSe4{GhVs(T-t=5V7$bwLzC~pUr}b^%JuPe% zk&3+nA%7%YdL7(3djb(2%#m2dFNj54HxJuN&z`#1jx7t)uIl?AqRNosW=?@*b5voy zrPXg*GN66Aa1nsp#Em)lBWk^Vl%(Ow5XYt|=8VlFRnr^cBe6DDb2H%fC)nBgb1_9G z_YME~#rh6^_o~yDed5(p!Q$w1Vp>)w9h%rI4u6=8-qQ{5*MSE~29sbdfFh1p zPawVE)EQ?4s$owiR$kB;O4r{INxP9g?&`3Jf%3KYsWg6$7FZRnehkclnY2_FhGd8a z0DlWps>G`8Ual!%@xa7F5nNH-x|w%yh}-ulJ8aPcxZfBzNGZm=8rn^a%%8A=nltLf zJwYUep#W)+dS|%koO;t)QUw|OW?$Zj>@Wd?1hEs*t{$Rr&iI}~wygoyzMP(zppiD)f8ufLnbT8Te6k)Wi&gYn`|( z#Pe(sjC~O6%(Vb}Px9-gnETDU?~x2kf~-x_^_%(&ybHV}33$;s_Ff`JZ&fK(<;blD zn9YxV80=F7;*b9&SZ3?S=d`=DvESma!%6(m%8|nde}D_r9BX5%mI25)*M_Ixp?~rL z3>Av&R^gM4&)To`JV+YTHK3ko&E7n&8DGfQcG`dzfAct?axv{3NR=aYUGRt+K$FAV zZReVIQ{w8&n~rZ99poo-jMA5DxqCY{>3VxUW7^Yt;+AKA@~Iu(j^z&MB$GKS%UJM` zW8sFm?3D^5cgx*oxc}A)_Jx{%Eq~B7WLI7|)!BHmlTsL^fRYZ^)7`hwyVD!uWFK~G ze@w^HbDTgDealmeuhGCQ!~BYoLTA_o#aP1HdsuGMZ;8Nf3a~NfU9ykIzblA=cb~jY z5}Xb`NGc%;Pnj8*1|bgwnIPI`9QCSdUeSat_(flT28^Jl@g9jc77bD%>wnOO%;i#! zj4L-wyi5OAiy-3yu|y2(#R1`TXoQwb`qmb_oQcWZZjB+MSu6E36o%?+X)ckhI!4uN z#Ya{&aJ22>@-5xH9qtyE$5fb|R^gcteD8ObPW=VJ6z|;5HX&fIf%)ThakY8em^bPWO2;sPWg0wzk7*qL|AM9A zyXruGWpPPEbGeBu8$cPq-YoM@H3Tve>t7eC z$ns|>mEt39#Im2`;)F`Z7KeQ>84HSV+@>sR)gn)^<^6TZs*gfbro=%m9z%aK1$Gfc z=a&C=UQcSlpZz&6|dFh6A*klBioG*&5mQz5p0gpH# zqv6l(kf$bNHFIJMDcGGc(We95MJMY1yxo4a93s>qUA+D_hPZ+7g7q_|97>4wuQ&E3 zR_;hlG`%w9C7m4a$z-W1ka2%ZZkaZ*B*+Fsf~C`HWtAZD3^8PCni#iNdpyi%xCKWr zDYX@z+l?~>hxQ;|}wY( zgTDT(kynzRz&IX%?15NDoXaiGW{zzA_=Af)fzyyH%BS| z@uYL$cPj~t+rwNN#B+bzJ+1HR|A|KhLFKOA8lyi+;e?1*=d!ps2#NAyDSCt~AiT{Js(@1e?Tr=xssj3f}}gv`ZdH za|vY46l^xWu61XFksLZ{x~PNRfKNA~+|Z$}f0hq{|LEXb!ZS(%*I6AgdkcF`XALPp zT|4?8O_mh?zR;)q<1|DN%7kH>P4=7mevB%hO|cJ8$?9?XReV7JY=zM>dbbn`qk=;-{c2nqOESve$`rKU)O&aK8Dr)feL0(P33vP}h`t71)gRI4z z*V;Uy|K-;bB=_UqoCecgGi)tu^k?8&3Hd)HA3^bV_iHDFseJ}_C{rw*+%byF6W*$I zm|Ky;FTP;Q`v;o8QV>6pWtxX^PA;(9eInHgNC$s%d{}wzyhw@xxgjH*n*oET6$0l{ z5vb(mpML5sbeS%ftKos&d1i!`WhEU8&r&p)sxJ2X)6(4V)u}2#4;~K@*|w6)l8t3- zsY_^AV%+fmjnAQXT|p2e{%ZBH^*K<;oN1W6k?MT?q(a??(A1)oUPx`GS}ZxTyP3Sg zhs}SgYN1?nsEv~X{7#VsCWpz9GY}%C4$k1qEW0$OLa*ZrkS+LrSKGF=O&*$a4L9HC z#`*9kPHuwM>^DCbfO|Wa6)YQTW03Q5>)QdXT7NoI>wCqRNmL!d&QuTk@N9yryR@}N zpZ7@b^aK;SD)Wv{Pb?wGLen&0G!9sv-Q<709fwb(8X(v{a`=EgY&{w;O|3$s8*KE) zoQ$b7r0$&i8{WlG@24L^Ko z6(ZAwL?+UXt++#M;;>Xww+$O%AsTzT!o_9q3x42eFZOWiQSa%JqCu!RFPpQkYcqd~ zG)fHs{QCI*k4+@#96RxSYBCD@gUzY{KsAv|Q#!E-Yog26j8nR`{r+crZK$K=Go>TB z!2`F&Q)s-rkt-@5_CI|1@u49kMq^iOT;{RjcoWu}Y2=cZ`JFD9*y4&Zz_rIJ0QD(q z_%-|njLjqnKK@L}Ll&N&jE==|_q2aBLQGCpWN5;(PqIM6XV^!B1d3OZ!=21I3Dvb^ zclDGxY^^1`#Ln)WG^R_2cG*eRAn;y(FG?gp(*uXZa{x5Z8M9DEJ&Z5H@Uxbh9OyX6Ms{ zf#J7Lx3tS&hj}-13c4dH&|EF2@{kS>fRUwX3^YzdjdI_sVHv8F;DS^yF`jQ zZE|5U$-DzQC$Sp97ZkT}n}YqvGE@jk>a{*=$O0Z};Hc_L{7r6Sq}LKFdkX`!O3zq<-7isdbHAr83uNrEB<~J6*_6j8pCpUr~J^=58QuWs=j7R@ZW%v zs$$z-B4LhX<29Xev@AcNu$!zF(7p10YxVa-I}R!LlEV%T38y9+tap>3#co88Y?_WW zfU?KIXcYQ<1WmoyQU-|nKACg0{W zKwEeq4dfY!scZ(aoJ8rWmzEf^kD1?jPH6@iA(Z}fFU&V=$HO&g-!-?FKj5Kh-$s&pPOaQ0Wy}6m%Rks z7gPAw6_C&c>g}CNl1e2-o#HI(xBum^aPoeqP1gW|>H5fhk5#axL*`kDXPJ{O8J8f}UfbObl}2P!H!{FlJkM17UB)D}~Z zwnoEr&79sUg%OJpzcgHGP{+_@mTqUlxt3>BJLsZQ?8ASX-dcRdI2#jqc#mGZz9^z% zhkMW_03-N}PSs$h<9T0S(7BJl&?Fl$VHF>|Mwwb*Mryp1CfCEv9meSu(pAgOcZ&uG zqN(>1?Y_g$ymWWeQ_0`J!g_a;5{*gu9%S9!Fi2soe44BI3hatUue7JS$b0x}Ru!F# zNyg$&)$V^ieJ?AZTrWylryWt?jIJE^+p@ia3EgFXu9t3dNpzeI#|8@SE#yx#`wg&# zq`jU2^rC(*Jiz0M4$6xE+!hMKtcxsSugRgkUvwPM9KeONMh$}dsey)bi2`n<)s60) z&-fz)MDiK7Lg_J(2!yN=S{wH`i&Q*ZMGZ;o((iv)Iy4B=sFbNTDKO$K8mEv5;jEMr z%$OqV1BO^jk$lt7w&$BP7W7ECf?dT!Zpx%f5-C0>lWHF6(1#T^se=299GX#MYl0|v zk32CBa!*IGbAi#7qJbTRMK9A-je!ISSMw;eAH(3P6RgDDV&f?o(%KW4cHIlVkkxM3 zai)LbQD>GPEgI|7V2mArRoHAQVAIjtyq7rn=q*} zg^+n8Y-u6WWWmjsC^iB#wU>=WciL^R@K(3J3LUPbmz#08E6a10ZOlwG_sxxgUggnL zxI$DZqYYUa}crJQDKgJZrR;1fFYxQUe))hDdeg*6|<;+D&;PQQO| z8rwjtVEZ_Cb&jcaevczB%@aGeOpV;`dI%#H7V%7b<02gD{Dvp2K9|jC!RE1?xa7yv zS(b$yzq!ER0vHL)17@?HbDLy~wH>ZMmy-_S@`On%)(qHoB)1Gn1JFcN!O0O4l-d~Xj<28ty&*;&aU>d<#mtK z8Rk%rVETCFC%8wm;iU!o4<9-{tEu0L7Ob*?Hn|LM`6@(rQ1S%Sxo&XiqIj;?$%uE| z1xHJ2HpAMc_juM&II5XmVkzv>c;F?+O%)mfy^L5gybF@T_k;bli%)WY#N>aD?MVZA zR`h`{1j`>B??h}fT&7Nv0IVcmdz)gj&Wh(-OZeJsxTGI1__QTA;SYz+*}_2&q)X>@S^<%~)(S*L#FVxCwy;OgEsu2;~H= zE>OyhW2Y)P6BIw_?(pRtcip}Q0x2acxOv(C#oM4CyLA^a#4wUX;H-bN-&|es=L%Q7V!L1glvoK@3m6wXAt$K{x*q)>D3G@rHV@jvhyndL6!L-IltSMP zL){Yy!Y7cS31tIg0s4Q0MQmn=JZYlGU(MYh#K@k59sHNxC1_B#3H!6vi>s*LdeQ2^ zLI+aCa_P4gnRf8A3qw<)NY;v!zo4723f5O5@UTC?v)2GV4fXoh7MRajaPim!q4E5U znoqk4WyUn`v_&eEee#V!qP0?C|U{-2ulq42g<0WzmK(f zu+a{(@<5_8gRj^41IO8@xcFRkgfaAq=Bu$Ci$Gh=7j^#L9^xFRTu(swL%#%;=coFO zvT0!=T}&1X#wQu>g=>E-XdLeL3SM0(46kBMB~z4K zS6gvCnZ|Qn3+6#gvt8%Z8Z}oTDZqUiakL`WSrDCkhHkMydnOJzh{E4ZT+ohnD$TuF zHD2f`E$ab|qAv}12)9u$vlz4oKtSuTF7A=1a$#mle+`l*f zCNw;nnMKnWiqC_J_gQX1^lOvT7}|MbL&FPqUn?gySAY zX=YflRA2fYD z9K9cGmQrxjO(B_CIute?l`wB*T>z*K=tEOq5ksO4A|d0*x6-&F2hK#+7PvfNuZQCn ztaA^0a`)&)V|l6%zteXz?nPzK*mDkkOCx{T#BX=r6UX`md;o0DTvoxfAeKlW^W=+B z^2yK4GTetR@r}snRSz?VJ^@7Iz2`vmF+2^|jPJtcI@38RpQef}ngi2O#Y584r@{E~!pWd$qQ!o+x5jWpX z=Q;#SteZ98v^;fE|A-vnw8YI5jFdehcwR_p_ymDT0NiD&5 z!SWs+d!DcVu%uIMi}a`2c|d5s;uj_0_pX5M>5dSshAT897$iHhQtlMU>H~i|+lZKG zz3NX*mQcVriM66Xs~KamHEmXXyO7xd5IHTs@}H;lc7`}j%Bm7-S<`Ffa3u?GVODhY zt${i)AzFUq+eo&AQuXG)WNL4?hpP)N)5LXOOl;%+l4jzv2}r8c6MnmhOtE{18nGG+ ztWYwDBFGwc5MxiYYlE?RIQduS0?|dPne0Qs6@_SeN-61?%VMu)L zO=#8>OSYbm2Ve~a`uGFs0lR8`+^kCoP3sw+inA7M__Q)yxVU z0L?cRO7jnWlW>*e5Zwt-8GLI2%^>!Ix_!>~MVpIvNH`Uk-ah(kycTU&nnAL+7wfSF zbN;JHrIzUrAu0@NX=2a(RnO;ug3e8C-k(CoUd*^ZgFT%s`!0V8dU?^8sw5o{B`Nsy zloc)KjyE_>TIAXptJNNo4%fVuxFe{TD$6FF3zbqThJoyvQU$7Oz-J^JC&|vRIH} zLgCz|>Ylo4Qo?rUTYr^q=WUW;3Vc$)d_A)0AN_@*9N!_1s!Q7%EiiUoVLNfHVqT1R zF5&ZOv<829x+h=VH*{*in}cKgD_Uza8TKs{-1%;E@s+x1x59rKm?dOCan&+` zGs&rwFL@TL?{B~*BFU?0H*8w_@_XY|vO660B)L`=o`KM>!tYxb0Co+Mk>mvH0j4&C zj+NMI7bgNzQ9CVnKGzJl9W&wpDPKb1BG#HFbq{}K==9Z-3m??(F}|*3uFD1a+;L+^ z25;B{Az=OFec4aK_)Q*4WI0ySPp~}!17<-ZD+e;Pc#IEV=*>h-Y#$QI!1UNk_}S~kc>oRQAC^xg%VwI zo+CE8wMtnXvkx_noOtp`1@mjn9IKu?m63nAR;`ZkEg5Pkcu$S6v&CSM7rRnX=1PD2cT-2`=xQ-}+q_{_Y8HUogP6%#L5oMimrLn4#$eif5qHAsd$9%kq z7d7(wd*oaA$5+}$^SoQybzj=aDs<9M#Rg|fF9RgvP^a{%P~iA7*}KZ=r}KZV^#+N1 zGIwmQV%P09|8$C<16(54NCS_pJx#g1D-N8(sJ?6%ATFH@lSV^|e9cI7o_9t6k^*U_ z_Gj#;1aS@mnTO!1lB($`ILx&^Niwgi65CM?<&8i}TQOa>4Rh32N@Kc@LO^Y+g9WW!V*~LDKc~f4XT3)dAn$6f695G4koJ={Em~**>}6@N2xupC<%1w%YiX9@9^D zk)7_+wk4w@)8hnYpq0ri103ik4*Z~-Fugm~;IDj;zth7JIVTNx91-VKapU8;4_K?3 znj~!biVS<#=g0P#eoB8A<9`-wHS4q?AB3iv=SchbXsGuo`76=GwfbL9FB9(|#IGqC zQblI9dq%^rKd;^_Sf5$x^%m|sv|`wSVt(mz_FpdO1=Jc}UL606E{y3MUW+dwtmFpt z(vgW|Eno|G5%+T7&>CzUktr22BDwbBId2ctK-9tV-z9-RG=YDTVF;4XULjJq0TS=a zVw7NS*76ndq*Zs`V`q-T>T~N9ntK&lpMON?(!!QHAjr&u?w~0-a~V)(YK}J1LF1BB z{(lRFETQz>sQBqbhbWFen7zn6$KjJMAPa zN&7W)y+vo?ZgPJNr=u9kf2S5(*$lg{%C#-4=Q@$S0_W%cUUQsn|LBo36gR)jw-13J ziiE%oF0KcgpQZ;W@_JFcZ2(&(6fRrji^bb;Am2Gb2~f_~hv;U5TiHDs2aK6u;}>TM z+2Wg>S%0|*8S=v=-b=mV{s1K2j}vBw?T0)SQ`{En1G<0Ys0hG36(H%KwC>aFWU<+4nH+!Z6Hy9uiC(o3iB@Q^?9=MD&>}SNp!5xsa{k8a|Em&@s4)!F(-1S)Hs7PI zrdS!O5^aJ2;mb8|FlZ}sx z?1aQ=tAL>NW(LupC13Bp@S^=SH5TIDX2gHvwsC(_HFNBx18`I2veh)O;M;%E(QsGm z2hZl*&$O|$^31Aa+h8pmk^r&LcBB^+Y4Z%W+HG9|;9h9O;CYyMthI2eRp z1*`*Az6f>?2zG5|o{`U4LLNO-7cM{Luekjm{dIyqkT_rq>#zju@-N@30@oHv!0drDCKBZjjKxy-YBV+-E1=gs9JEBC;}-eN98aUR5S&=xX|Fg@bd(D4Loq`K9OX{i#c)h84{W5LgbqN6m z9nBPb`T|_sAU^)B53QQEJjKr{;27s=@bY+U4A`UKH2b8d@_J6BeRq@}8v(UpoB zQw~iyz?i75q_GFa>^HKmj~c4X)M8o#?fnOGHF0X^0J|o?xZm>qc{karWWj&xKx}jA z>^)P}&Eo0#$pmWANQ+X4dKh4?08f2G&u=NHpWvR)5f*F%ghTAuyE#qW&6-Hv@T-5$ zGw)l%TTxG=teRYgo|XWSYvZYlE9PHGFpVhQO^geIk!i-y5{${UAIB-ubPQX<_0PD} z;H{wI3wjIV%!(nEEYv-qzVd&jQ%IRBWc(W{vL3Co`CcWbo-W{0&y=9262%Pnt-Av1 z9$28F6fX8w#NcA9oapF6m3Lh~awMEeyb5d1DljyzgQFMzLvcG?r1&k-*w02%S zI+5jYm5yA{)Iw7DW+@7)H`X@Al?|HCaI29LpeoKd&bn$7;t1`;z0G+X|0SN4oNPxh5u+0-@?JyjiGvd{-9J8vclww;J5G9~e|U#k zv1G*(0~#+0A@buI%BX+E0`5qh(}-}Pc$L&FAuL$VD3*Rv%-u1cx@CwGpx--QQ)S{e!s9PXUcYHFvC@4je56 z?H|(*LTs|GbJ7=i1Y({f^big|;|UeYw=~dyxo=`jEZxAwl1+bzqslckZ0e2u#K_~X zW?x;tC7I|bGmy68V~c8EZ=O#T(xZ_tG(=ueqv=FRvddH6>qfn|JcJE}g(PWbhivoQ zRBFKCa^lg#CS0ppefgl0ut?4&F9C^EpcGfypxOj*nDZI3o2q=?H5quqHHju9l)X<) zF|SI3%7kVE!iIk#P3$WQ)F3_ow@Di4q*0QzD~6(?cSX7HQKf6a3wTMcYVOZLk@h)h zZA~qKY-sXM-AJ)IYNww&ucsV74u%dx>9{$ShDw{p7rTgK*s?`}anu*jGIo}tvTtVp zb3)liFg}c2#d2u#4+2dl-0RH|@@K6Y??{nmrdj!%1ImA9Z@ZYUYdBDu645SvZtvmO zfi2aaIei3lE6A-+S=(Q~ua4614iEdZ$QZ=o@86NfSfHx(s&yz5h>cPgsAF<4pi4uxVNLHrF`v>(_j zp7`9B_aWP_KWBJL0iin3KUVEt9Six<+kFUiVvv994Fy;fc0Asw@23w-vF2}m0QrCt z8V}^M+=C*O;$&lWMxcF>iO(41y-}**Gc2MI;tF=rSWh_LQDu{9-^5tq!yc_#%__MP zISX2?T#Ea6F%JxGrx1LJx(V_DmzYelxbpV^)OB*V58Jk1YfyGP%v;ZtzTWd(%wB+!_Y(ilew$cDE8 z2`5NPEqB2c45Fl{!diO~j8&BE^Q@Ab2ym;Uhd8xheKGn-s7hoSw34+Fp)ygqm%X-r zRzM}dBbHGeYrmJ^tk;sS;`R;O9O=I_YO{azNsz*il=jF==-HiD*5sw}Dei%)=zL0d zE--2X{uk+AOk>@xlR-Az%ZFE3vS;*O+1z4FzYkpkii1v7l}-@mf|%)=}5 zy+l6ZUs8guA<)O*9}K2HQ{O|rb#W9BK;&Xv!O>H8&wu~29-k|AynlR&-TtshfTw@N zer{kH;m9b?Q_D?MroyPzbLY2@sq>S6sxf-mhiJ}m9)*|Ab?X)X0G+u-!bnuyWap}j zdu4$WEaxqO{kX^;`*`ch>*TvV(J~vst=GBLkO0-AWHcD2MErT&TPRp5{2s1k5BgH`%-XfQ-KJ7SzTTAknW#W87a6UKDgKQp5>NvT;c6Ji(b6l`we z(}6%hP(cFp^MFeV63KM;Cv|^QCC+IQF(dHbLI_N*Rgv2Fq~O)`PB|m(I?57=rTpv4pl6+^vS^_Z@K57IdFf0U##Dr&9!6=Jfsv zqZSWIjSzsA_WX;DIdgxg`gDpnW?l+tOEf*hQ&g`0F+aH`BxtjkT#kP@&>Qpyh4vfj zwd?PrWpi~`v?63ZXUXvPL87|R;yek1_jSf{N{&Op;=WrI?urPuVe+9fV<5hv+!ZbG z{6bWra}HWoFg@TK%m*7PW%OHiIL`7RCJEG}y$QkNAL`98+9ySK4zFk(%$*r%zh0ee}NUj{S5;^Rqe z%BbXbD?rYMy!wC2CZOU1;q>SedoUA?J`5P-#dx}=740SnNxVfoOipMv9_l&ftCwX| zYFVyNOSkv)ABO>1NYLo)R3g`DF@JFrYtR-(SxL!1z@TN_gdMGw8y zg8G(9MWp&{E4i0Hn2kFlddR7w5p7(TiG0b()*JG*1t8>8dmp^ zX<^Vq3D|c~6CuTrMnminbHowHsh3vCEM4SIP0jOgXb%;7!Ex$dM;XUc|F}^vybo?t zZ`fWbQs$4poa0Zt`&Qd@Dv0e`!fr~vcfC!wSp9ztSUXTcgd4P&o(5sm6t(DEoT)~c zTgjjQ+G_!?|CiF50j?Kb<@V4<)N)CSeK7TT zZBRcTHag*pxkGF1B7y|2#~0o|aP1}o+9zndd6C--VVn| z+fuCmDkyXdcG0KV

^`ouMeUFP+xsoI8rt&$DdlCh za6@%8^@F3x!Uhoe*zYJ&G&5Mw8F81ZoqK39}tdtM8yZ6L$kzZbW$1lYX3fZdz7$6 zsrM8SmQ!a6G$m+>Qphp^i_1$Hz$6qtk!Wfno7${RIL7gwq_!o0b;%1!Bf0sgr0{=& zGoN;`wh-b;6gOB;Avqz%GSw$U5dQtiSOf^l!}qKD6ja>~gC^+LFD@isNxUj+aiu!o zyH`3B?Tc{tIaiiCV(C4It7Vby?|%1Elxw#bo`hWjjlMoC_ng7iOnPGWiim>+QY3%+Cgo0*^UHrOdOsV4*5>0AO4$Od1nFecDlF(7&ZS72qf&Ha zXZ*;AI%Btn93_)Q%h4%@w4&l$S<4>FWCK`I9}$p#D_;zn211)7$vDIln@O1&%X@}t z3vZAS5)r_hgGqR7?kl~;TYh;uygAy3E;-e8TWolr<&S1_YujZLNNMA?C$9)BP>nM?=gEZu`-L4QCs9WTP*GXr!6wR7MwfJG1x zPMw+LboWVo?pfXyp+7gkcVhp0WSiv zKmuDK7L27fuoTaanfYq;RHuFjcXbLciVW=q=A+oS5%qwq!XuyxsfvG!eAkB39`vGm z+FcZW8p#|Peh|DEg#kqEsa2pG6?Szz%3I!JtWj<=s9HCrA}(&=6wVdJ{-a??HG^)T zWLa4wmFldAX!^FDUjC1W{N`rlA-5ai33nUiQEbOI5*b?1h$wyJUSTgBn)bqy!$?do z>WLe6mn9tAHTj0aZs>oDn>S~_MOsXVrIW32C8fKLvf5{i)X)o$U{x_5!&DRYfmtq? zhI(Td)$`>=n0@eS_Q!VMOjzv;fiR_b=jHxK*y1u)0mr(NX7EK_kA;DrJHKetA#{g6 zfslXdj5b(lDFOM(U~2$li8Klo8G2T-KE$(Ya^$w9npT(nI9Y#6o;&lj&^>qUf+Cvy zL`s>o`|DOtFfEKVm386i_f~1XgEry9?3$k=K#6oVh8iDrBb3p8?pTR3u&DQ%Iy~)K%KvMSkn3g*<~i2`MtsKGl8lq+}9gQqjM!^`>suY zPx|Ed2el^*`cJS61(Y42;f%n7CUaHrKzqC|qacOBl3jl_kh4lZRHbbd-1m(i$i7kI z6B5D?_RzKE5#e}$m8pbUNKmuOq>SkJD2{Nc{g$bxQ?5h&a32d!am4-((fJxv>!56G zZvpA_Ldbx3Z8 z7oG+&*_s$6jZEwyj6&9Hqa30N1Ldl?4uP+UAe0woGN52CpsdLND95B{)WrjG`_Pb) zKPqg7^+aQ~+_&Dn16nO}GhX)rLiUa6FI`G2l}20`cqipUEO;}VW?BS^!9U*40!oS_ z(u0360;uAH3>*>P!$++HU`RahFEHf?ZgEHcIkR!(!WHQso&?o5Rb%iWRkK3=+zn(t z>DSH;B3T;Vv;s{e`Q+dxLp*F}I*^kFmI|Beb6yaq%MCNz#`PbvRPJVd$G4tqCCTyV=l=FWH*lO zH<&(moOvUgD{PXvc(l1fZm|%UZuj5np<7ZAk*7+obp9K`Ah|2*ERM2HG#0;hsu;>S zd1eLMGxBm@di~o6`MK_(t>&6PSrE=~Ii@YzjYnJGgh%plKUSI|S{+NI5 z!Lrfg_`BOPSv_vu8%60HZ+-X)eC6F-P`oIE;(HgEf;Pxxue0&nQjhVM*uNg*^melhJN6Z@g~o2o4X_?1g(?sbDSu*Q;g^(NMOQ7F>HF|pXDAbJWv z{##4k9oRy@#|lS4!mq4SPKl*@^gVw+)A4t;c}O5vtr!ilx-L~MboN*C#;H_alJEUE zrj2p#t9CJJ9l9P3O{Cp?r9|}amu=Hhs7Z!z0QG(=>cj(wZlpkg7p%$B0 zjg3{E&kc+MfOmH&-Y7W+GEL`WlkRGXTySxW!Y!B!ZJ$K0M=p3LMa?4QaH@Z`idXB; zYlyCrrOan*W|oPZpjkhgC>$rwlrwFzzHJ@ENQC#PJCihne6d@pfk!my5NhNWPiSUD zKB3%r;nEz<`Zs>=+CR!CWe-b=lI{@Ebjd8j(l)=jR5b2GpJ-$(`4KNRDR5^e=c77M zWaL=&Vw2t0vxh#a@c$w7-{F5eLn=4n_h_6Iwhtywsg!p`Q5$UC$E{$ zr_Z6}39~ovA9_-b$ld0W_l&Z&)MPO4By)i*?_#LC_=8dWZ=KtZYkSY8QqztOA+<45 z`^-&IsX;NV!sU_$w@E&-lMwUM`hI`Op{>+mja9?= zetdF?2SGXcz`o1|agy}~BEIC^$0FB-WeOtI3vt7MYn^}olMuW6RAD-*#P4~@P<9)) z^*bZ?6;I+m+{+0^wcwA7l^8VS9UeZ1nVBJy>6t!3-#(VFU+`aS$jfhj7+?Oj7kkE` zO*9kipf z^z{)1)ST8|&BtsO{iHJ<9@aktSEQM@a6 z4O&{8lT@DL3s`PllZdir zD3yhsDob(S?3ta_=0|I+5&as#`%*e$!Zt}ZvdS<)%77j+WhdLTB-JetAt^~NxY4;R z=8q*3u*>y1QqcVB;AFLy?8a&E0OGfvtgkZ=Xk9|CB6XE&mB3Y_o~090ai0M^f|7$c zWz5nk`XdLN3Pje#pc|raFE%p4~60X19{i5ejE)eLH+WHb`LKStdXI=?A8jaQt7uw^zVgznw0W zwty;kAeolJMjAhkftW>Q*ROU*71v&xnQ0v#8yMad8drZ=p+~vIKK!-}#!nzZOB;4F z=JZ$&WxRQS!(UI%tEe-NTnXviUSwpt6D^%H-7BkPo5+IP?M(>0g+NwCM)6>|>DY+^ zIkIKjNpj1Ph&%dsp(dv$--i4fj5W7{-|&%ep!PdZ3g5SRnD zu%=)){PPR4RfPa}AhE48CvMRQ_r-q#8+n5htDc!Ot19STV^qT`ykVpm&+`M3OL!2Y znq4CORewO<bO1JzAkO71EHctk@-$^DB{)``ypWc*d3r*;eW4Go0oo@% zu5EuVgiveA>z5Bw*k#*Gs0iyAvJ|pBB?x7J z_PBp5>Ni0D>M(t^7K#vBB4Md;)1`czp?L1qHaNc*MUzjuJf`9HM9QtGag-zWUYV~( zLN4L?t-d=?*rNZUBgDG*nRK$*^ofsyN+5r5okbap`X3t(BBdK*bC}@3j|$b{p7<^$ zBk}M_3A&A&#yoL8=WU!lGE1o-J>IwpjGd{WpFCK1*N*c2kgvKZ!<)^j-J1PHI*JvQ zGjF&%I}S9_k#C&Sq352@I4SNh33DAvywawl+k)RBOzd_9ibB^_93izA;DC#klumz0 z26wM%X)R6CHhR|a!fKan|hbIGZY3F;y>mBh~s@Cv4}RoO3s)V%D3vSF7NpO zL(+rzzPx{-aoP)b)kOC5EKib?2C9FDhRyUkQEwSb`3dst#!k4QwfUH`2ouLFXJx|h zrck{(94Io>j48-Kvdi7cLOuTEJnrSf8N-ye2C)Kb2Ywxx_U@;l1sT7I7OrRGKcGY3 zMS*meoS%u-gf(spO1@?c?X+##r(JHC!c@Y6C8hN) zbOY5hVS4QHLKp55NxJmtg17DCkP%vNa&IzvRjRR6K6=o(XYJJ4Nqo0w$x4Oww`6GZ zG9XLl(3EESfJ{#|)ncNN7yrBc`%*q204ucZ!-0O;A|-dnywP=WAZC)yezZu>a=E&# zE9PwbC-N3R*)T#XwWKIl4Yz-C^8nEqT zHC^_MXv7YI4nJL7$N3ez||6QG&U;X*XixyyrS+_k0SocVObfhq41G{+{Ugn&u!5{5^;s zLEy4EFQ|`{%Pd+9R9{ATZnm{Rb!Nt1FrAxwW+%7K?7_NSC5gG^e_|9iVg$J5j~ za(;}%D1yNUj=c2US9n6J(ld8Vl%_lp z{kB42@UOaq4HtjDQF|m;F+ktS)SF}^Dzs&JeXsuOCn=99QYLT)!!dVtc3dj(i@Fr* z{EYJ4lyoS)J6(UJ)*o-9ROC%*B&IJpEciYdpC;?7Ae{B+-mG@KoPJ%x+nuD(r4iZh z4Oo!q=wn*ZdM*Er{!Y<=Vo!Du*j0ef}044Hp*y+?j`;@c!1`{O#JvxEjs zsnH6G0G2D9y0fydsdFK<`eo3*ZZg%)`*X!@H>OYxTc1|RJ}%1!`su9=j@7D91z2$4 zvV@xZN=Z>l%)nJ%$0S)Gq0PI6IF;uYBG#81sdq3VMO-`1%mp6}lkzFIUxoPLOe!sudSm5Ta#hTs7R zHWBrL_3yY@56duWDu>{(iBSu}%fnS?gign1KvH#$PZK|0%5boC_D>-+R+gekA4-ZT zkuW5x5C1^MGa)`j;CR-x1&D3)!^=AoELjjieAR!Wm~PY`^%{Lms^GB9OiOEu`0Q|8 zRm#M=kCW^-8`1w&9ELRTV+T0{*96mrzf1xiAqWM6tP20@9xewm{CP0~OxJr0)W*Dn zANgh84UM)yc*I@)N;Da~;(Vnhs_-8}&Eq0U=Bx#g^)jh)4sKp`ZoI{#E_B_`S-*zK zyUc$#Gw-=lQAL>Tm6s*8O3nQn^>}*F`^od1eW{}9w(k}dVTYEmKQyorq_!#426UUr zAO&gxwBYQRYIl}*E3h1({zX$SMS%StB|$I~b<_5N)4@R#^kPcR(d@PJ?~Q@m(4vzG zN$!Y_cb=z7Y%(IosT0pKG~S4coW4zrAWlpxXLD~`$b*pLu0TJ5!mb%I}7V%M93t7~&MxwSD#`HFP zxhA)zQ8}ixqVr1BU2`l7P{)p+e}u#;cLtU1s}^qca;}eV8uk{e06Rd$zfNt#nXEiA zX4)2iT`NN4mqRkwex!ff_E2LzArjEjWd|jpE6;b~!u0YGv#c<_4r*XNt}CSy3Ilhw z8L^1WdWUj-jy*J#EkIiA&6Qm*#Cf^<_$parp-lQ;&=EnokEvg|2Q!p=E1*>ueT$0c zThxNNbS(Qo7uicci4?CU?eXABK06{5*YBKvB~VMA4@QA_IXT6esM)9LLKoWdYo2=? zS?IF!bDVi&Xm*)Fl|fppHw^9#(KpBoWyH@<<^mO2kICK~V}twq(fKV?kmwu1_PruU zx6`(FmAprK zNYg^>W>s|xnRDTM`enzUhgb|Rn1FH_MW8c* z)O`;&OUZ(r>~fTO@S!`6OJUHIu+b)ehk9k5C>3ozZB0o=aSoMCiZ3c2bTUwk&JOda z3toq36!0wuV7V?1)IHf$U=Hb3c&@u=?H(|@@R2?r5{K| zDwBo8Uu3%^%r6RVA=NmFNB$q>dsBQnI^ONv<#@zA!;(4Uv)_SiO5}a!0wuWD+x0lP z?WAd~IuS3-nc?WH&Pc*#wp%>`E*a~9)8)G?`hb!PIpBBP$}p5y%kDD^zrTCdjEvMX zIrzvU_v)}h_bg9RspdlgaC9s?-?PC3O}=M;Puh`N5748BnWgi4PhkoRGTrSeycW8NCD*wDxrECv15Hi2S8%p}PO}N`wys^~ zNg{&Og(5_xDcizr+zJPZy%Mzecwu3m|NntxiP821+r4)X^bv0XSB^1-qyjvz*DlPA zm6&4AbDgAWl-V=2m6OE1zfL6VqgTIac1?2ln&{>w9!vHt;r^U9dr8AlZDx73O8Bj< z+Jde$m3OU&r}nemZz0ToOby_NV2d_`MDAxhPB|$!h4qnYS3VZ0x?1_;JZW#IVJaV* z2)guGKK-HXx`w_t$LJw-B$Z{iFJibBFRx;(B5mSR)+N%ty;fkKV3ilHI9hUBLI{AM zvTndCqc3InW7~1cD4q?S1*+8?t%GX`&*P4x|8L*HX4sHE{XU+5zga*==E{|ckmV`o z<5g*}v^ImOy$i>r`#8J@ST9isLR{iZmA><*JPZOfQODvUMbi9*>ke*yu)vsQOW-I! z0*{mjty9UwD^TjUVUmfgWlv^EYm>%0l4sh2ECx|Y)JDp5u71Q}mB+T0^A4@;`{PkD z?0uWBcOOp(t$Ox<>I1(xy0oD6BAmudA%OH{x_my@tJg%F8_*gFv~F7yh)N%$hc0;k zgIH>FOK{(=w`0%OVI*xehQqBOdT8kthy*pUp7Ar$xC9Po|1pP+_29s|anzK=?@Gn?9lCz=cz8F1h9+ zFw3I8VmhXONlyk7rfLEz1D5aoOzRD{x1k<`U7@l-i2L#d2ZKZorRb*JZj8GxR@-M? zz{9Fe-dL(H;oCSZRjvXBH;XRnK>8(i87Qr~yz?+~*qPa}@&x*h;6iP^OD|PwdckW| z%A+_`)!wtcsMYzy?k07+pX)YkIiIUi{lw3HtO0s|;A>ESg7BrmFYtW_eLh=HBMia| zxTLuap>vXbD|U<{r``kLv#+y3NGn%`m>NyVv`u`?Y}8&=~P z9Zw;xJ9ikYO~%4>)(V?qIRPBGFKG6mI%tOn=qAxF;qFjzxp%XEX1F1u)Hi>jA&d}R z=0X{N{a|bA3CI8}L7XO-bJOj2@27-UQ;rBn5}rPJuSYX{l}nn#wk(4vPqtDI{=h6>_)WY?o8&vXQ<*QyRKc`48Oz=ID_{F#br$TI8t zznB%2tMPPp6LjQ%&PfTV)Dp35&RCrtPqbNo^PE76X9>mOuQNn&V+qYMtSJgkCe9|C zqkG-~&!SiAF8-zW5Ii7oDcmA^u+KX0xd1L6B+BlMLkn%PAlxU1o5edG4u4iNdIV;K zs4hfo;{`td)Y9@R;`EUb3#h>G$e|PLKuLAKZgL^*6P%2w{&|74y$CIP^IVZPw#(>5B;nS5Gi>1rcE(M#23yT<^&ko6_p zG*o(fHJijz-&2dM#4U4p_{mG8vP1w1bPjqSIVGbIHy+t8p1BlW6UC+9&4(tQYn4=#t zhIf?N*dI#xpM)SSGT3@08r@ER4_I8`*v}a38yur33mkpi|HcOUqGd3))3e_VfG0>X zG+{{@`kj`*H>yr#2M=W%n>g~k@ZqgB<>;~@Cl9(@8b{qAn4~lFU>eGulaa9yZ9Sv) zIBeF8z}IVuqYU`y(WsZY)SIvCu^APOI;E=YgC9s=*y=W7()gRA-xrI2Q*GMPa=QUg z1sAMi1S?N#5R=v7rCl)kVaRi;o5ra}_{9&3#J3S0P-o}CDSxqFw&W-nPZv#7laDS| zq*}2Ed=Pj_GNS&0-n2k4m15gVb@^$4?#VIBN^%z&rGKXR!|*^Zk|4gTkJteC(kfXF z8q=qcUu_ku&{UC(rUEGGX*_r)f7 zcTB|$riOYkSg7ji46hsqUYqmsx_?2^ww7>-2)t6*1WXm~3tsSlvc$u@*=G3!~r)H^a-A`!a8@Wg(SMG*&w1^=}Y||Sx15c&Y6MQDAKurd>$2i zEhaAR;^r2vizgAJi#aX-3CP4iVAwA#M$(c zdhCMqu&)Qmw6xAr1!>7uB-e=M2yuFh#9OytuImt%PJkYzh<1McnnsSO6Jp9(=^=SU z)==4VJd#V0dlbd=?(%Cdk;^S7>KV?k?I%-nek;8PWpOTlwj^vIzd>7py9@dxwXpB6 z6WA!0GJfE0wx^-IO=j$w$~kC-ynM-qd@3o~yd4-_#GfH8ap{6^+P1Xy70{cMtUz_b z>)3amAyWiAZJ43@6$zxAes31SFmC5B$x<3RnR}+PZ`}8W|``e zkr8^V-5iO3sMB2v!2W3vi;<&tKz5d>)eHhXl_kS`N(xavU{$jz`>`sP5|rm60hN3D zX)35iL_ubB2iETS(ZxA=&+Ou}e|v9CGhgY`=rH zFbll7Qq2KzY!B5FC=BC8F(zkObn{&A4aQL`tEv`%%0NQ0_`~8Mp>D39!(=v?20Fx! ztikLO=sP2F#YLuAbGD{=@<^457K1NO)1Dt7D+Ud3)tHZ-~!g|0# zu}g!GnXSN!jdiF{M;qiB#bh2e#IF(h3H%1Fq19>^wJauju|#kT$mCywO1oHXu>Plm z3qhoRnBlhEdiF3HVx>7waMGf4fwXJ~Yo{dLzewaiWW9z12kWr3w3#bB#EK8GW9@Ti z;E+Ln9v%nAkMjSAfyTBwt@e&R43)ZlX>|03zBHnY^)2|1G*5ONo^-xgL|JVp5weA{ z+_b|mYi4c-=%aKWS(WVw{SnQweAhn=2sO4AgNce<#* z*u({iABlw*v#GuLvttA)PwC;p41R`xP1QUNq^0?Mu+Vs5`|ydf3p|nYxA`@=h$}`jOd( zO*Vn`^In;#@6ZX{)VRVnQ?k;H-TQ(9QlF%`%JPx6@!R%R(gkpzhK;Py8ejc?_<5Wz z);(f-T>m5fCLxoL6nM-vetDbK+mO$i!11xRSGz5YY zr*0PqJ%JVeH(ta#|ET* zRvPn@7)wZ<&+-K$al3&iRzt3NkrpGxxwy;DwHwZa!3Ar@nWhSkr3+nGuIXbE^|+`J z*sGsl@Z;7P{x4z<>HKx2Mjm>3x3!KI^h0fr20ktRgVIeCzZV_|iOSRZT|V2G8e}e} z5xMzTtbo#KY}}cEoP36Vi17720^uUQPMWw9waMfuHY_tx{V)g6X8xuJK?i>-ZR(#P-USN5J?NW=;?@$h;qO!Rang&Pui6bSc|{&NhXwIpV&+q9Z%B zM4Oz1>AIVgBI!pCqY)HJB;WN?=-TGL}QNEb@4;2LYDT+31hDs4jJI_bRTc%oy{oGU6>@-HfP6U^?#kRbZFyaHZ0OQMN~ zjVbp#ef^lX4tp$8USg}cMx+q9wVwg@Kl3*QP^>6`*M?<(O%2gtStgY14?aRfcLq>= z6U8)Cu{2(t$GM&doRf;O`tCZiI-+8=DghG=%e>UjeyZvs;}jQfxMkeWE(7LUjr(Nh zaJN524e$=84c7y69bPA+6=V<1S@&H4hMb3GC%<)nn6+^I>hFwH(DP>~sY|x03U4r? z;AX&~w4W<(&w(0mBRp?gc23xrbrT5`T30tolsv| zDP(bf=-ar>A~CXCk?_M(XvAaq+DeO#CdQA9u-*Q{RqbM?m&`{xdm&-An%j_dBhUg) zy~#lL<7DM6@#m~<9QP#?;W0w=gKZBU&(BmqvaI!+YAGHhZpI7!t}EV7W9abnAQHD* zqfmm@T-mDZzH0E7*P{TFp27#Z6;Z;qy%GX{7*^-pkS$e_^97tqO6!DvNy50-)&qu@ z`Vjt*yOP(!iz>l|;~RLKChy!l-@Q+PdR>0uhtcFtt6hZmPFA3^EYe5QcNwiKwF?hT zu%G?2-o22p4CAfc?q7+&y{;-mW%)&At!SY#qc`9qxuy*r9LiP}^)8r4HSjck^EkGD zu)9b7HI})gvARw;PVHv!e_<`LwJ)>T73W!VaOEx%Ulj2*S7B5O_t}jYN;Q<+caAOC zlY6nB#Q*=r>jPjinIe5psS#BpC*J9b+v)eO58CVwG>U%S_~LrUppe%@Z>`4BLaz%W zGA#-WkL!TP%xx4(72rpYjoe5X?VCh@V`Ek8SN}foP4s3ug2SIz@X=NSX!e!Lqdkz~ zL1#h+Az~B7sBx@jkKhY>%-=ug8g-rdTDf~g(r`}PCF>ba)xslxlJBB< zznVd92j5|FjCOTu?h7>NbAT?-ioD)zOdzwX&I80JBI>$tqO$7N3E&q%w1TAw;%mrn z0|u9Vq+Ohe#cx~Zkt)tg-k*^41aJTDNOM6a70x0vml5|1rJ5N{Q{)%k-Lcgrr@{o{ zkt>vUWIN$LJ%^x4mT5LI{=vY1?Wt=u_;aabkYV(n@C&2Xi*!6OU~FRUu9!$s97jWc zU>|%v8vd8N!`M`dM6}Qojm&wf)aq+w1eR*)t2255S}oU~GK``aXbt44z6Zt1qnm3D zADI}-s!oK0V!2$@!zXk`ThD+RPbh=}p!3lgb!d(kwoPGh6d_LGIAc_Q?e*154sXA0 z4nJ&hE~{>Z1Y8%UON{4Ctmh4)o$r&uPPrF6T!0r!91R+6CFX=iLFgGQ(;WOF?u3cT zSno4VyQnlI|68F)($>+ZUNfu5*&tw=w2b7yEA*lYDJk%&!FJy8ICPU_AKy5|rsJsg zElc`N*(ZaTFRE}m^Khd8L{bvJ83C@IuH2~Px~tBu`(P6^1KjlM69jqX*o%B~2?O5}UF9h_EJTsEi^hg~ZBk?jlco9lW8y zr7!1VoiQp(D(%WP>a=y@rAJFsu>I#$yHZQ-d-6Ei2`F>{hhy*6a(;{>h|1myhRP^U z9sV++@?pmB^ZyWkNWY*>cX_bZK#vUBQtnR8e5%AqVyn$7!lCwk-cPb&&5HVJ@y#WrHEi_z4e1liu^sHw-j#2*bt>lS#Ev zR#q0t8>gIEr{0+w5{L87wayh3lJ^fn#ZKUrKU{6Ka{K{*^5dE)nD6py!Kdvilt?|fO5DZ z%RKy7u$F*25*q(pb_3(arZ|^2M)akx@BD_}(<93_+cU@#$zT3A!$XAZH2;cq*|r~O z<^202%o*~3=IBP@34@co8s?3}=ceV!)~5G~YA7l|?u$YY2}5uoC)sgbA^#kC9;-3v zHk{eg$9wuKIIZ^DFRmJHF+B3Y;^>qE>{N~spB1J=VK6KI=;vUZYg+MfUZn{+R0~AS zXxO*g@{HR(%%DYz@2fyC=!?MUV&v%w!AKqXdX>a~hZ=4!xM{!-0w(OFpbzd5(2niX?*MSSR-<)>-$|Cy9|AdQ6>pq?iKWL;Xu&9dl%^Kn`?U;F?QUvfaP5A zTMn~-dq<%CUgWXPcQ7dK)d+8!C#)D)vyJZzUkDN|+9SvT3(&vna#Mf<|4%uK#gG^x zSk%WAKYDX{3^9{=!zTP;T(v`C7f}z-{(v|O{#%f(w7N(aN|)p+FI9XwTYh1}5AO#P zoO{dRnTs_w%e`ehMo<)z|FHYO_$Qn4V}{0mpKoBMNYq9!A-^ID;|KlbR`!<53z{$L zL7CA;U9x8kgWv$92>DT5bFDX;v$8>DF=Q4Aoio&MgP+-O)9PxS$~xWA+?Q~3!uMjQ zj48jzS|bzYKNYDNZn_BFPWu1K$#*ehVVsm|W2Uf>5SM$Rf*$zMiBb!Pd2-MOUJvts zLVZs7QKuLAZq;Vc_m4g~T@`hlq;+g2npXCp3=+~Eok--VvQXgP`BwVYLBT0b;@fW$ zZgF@Eypkm5%pGe9ap)YF-!TjUgOsP}{~LT%ylWrM0W(fav7n)Uzop0; zA7Z8H_^799jl&tiPSC{!X>U#f&HWTG8w>=APC60qO|omFCP8$#)$T4Z55jeAk{yDj zhqKo14beShx%WNFZ;`DbfAi7^YLp;5IHb-gb{t6MHxX(F!-rcIItle@v(7rxg$u#A zOF-=+J)lm@Mz|eMdR8@`RKR_I!j|08pcHrWCv(V^stk(>Wx#~SmR8|ur9WHRZn#ylnLI|?<~7mjL5 zzHRIfzeIsQBAYMv8eO<*MQI=bcj>OC-^Tze(u<4RAv#xjd2j~5=7r+k&Ym-T!*Va} zY;}Aj#^S<`4SiRLS;2v^i1Q-vWRlsLqaZ_Lu%V@Zb(Db+oNJnY>=1LR1DO1?I$%?n ztoLjBdescRQ1-@-6k9H`bu*r|vh7q`8zltXZ5?Z#Q}dN|x;|4o;ti>`G3d#2MEOzp zw){{Lopuy+6IqYh|Az4`p;QWF#3{w$AY;BRKTJN(n1Ilzozjxz*0%*Wrd30pt}A7c zS05m=X$Tv(LoUXDwWlf*!A7=)&7577F*Tq+RSH7gp#GRXByOoo+YjAjTKDwi+iwzO z4%1KkkkO4-Cf{6jdC+!d;$O#5XWa7h?otrxm?FPkkcC2sA3E#N0$0YVG)#{^_orQlY`_1kYtF4mTSE(TgvQ$kS6f)w?w@ zA|1giN}<&J%14gq(0WXFups-MBOLhTV8o1meaefR#?&AkLZ*kLlZfS1N!-?TYr`7N zMt}d9>Z9;~&2DA`grZC61=Ab1Pk+1Kvgxe?=C7hLS*d6NdyJt{j+1)}=IM=Fot(n{en(d`jT_ZKbCD z{Rz7apA&{VQ}UOmBYYwde{}6OWU`S6tukc%Jy-Is5Q)FRE5lOn^Bh)U}dkL>;7l?ehb8itWEf6&o? zZ7ukbfhES9Y^X;ZtiQ`i(t2UHaV<2mb5(l#}CJa;#BG@q_~{>?2|5X z{@Crny)`|U$FegD*pxG#AtpTQaN(~7Q>DPE5F5K!C%%6n<)$-C)7NR(KwfZvFrW_z zojr!*|5ddsW|OwXj-HaVAnkrL4ZHEY61M|0?QhzK`b(jrqS94WcZ?4tu3nExAnvRU zSshSQYYl!|vQZuN&|upD#`g3nRFrF)+75Cw@L}PCpXqReLTGMIp4WzHH(OJy<+x^B zm+MfpVCso9513PpJUA=RaEkSRvU@nLgTnFMSf26qO9ULLIcI<5KheN(`eA(-PjU<^ zN^&3K_~NYM6)LZ5#{4eZ&F)t{+<&_3R^~H1VRN~-lBjC{r>VrI+{d$*!8E{`Uk~aH zh!pJF?8JdL#*2taKTWFqHEAgrHz6GU|XtIxOrBZu8<&Ck4SQg|4+Spg>F!m`LrD9k< zMVpQ$qkQE2lv)i@Lm}25SB%PNK*K`Y%>mE$Yv^v`UD9Va06^}8C9ivXYG7LHbh>FE z?LNWtQl!V%|0ZFSl~r#jX)VW`yOaj@Qog4X5)1IS-0{n=-;vCJaGc>R7?4>UxR!#l z?2KvmlccinTQ)TaLyf%qo%8y6AW5^9Mve7WQH>nc%SMjwY)Iqn^rzlpN?eUDLcRVE z4Q@n&0<1|#rMllr@Olk-d5 z&7D|m1ap#men2hv<_Eu3ho;f$@CTt(bZ<{q$q(e*(1uky4Q7JXqWuu$`jaWEuL0Cx zkZAT7Q*vMj#!@?x`7yjH2*C1>yIJv#V66?sh~MHu;`uc`sJQTE}Jug~~{ zHe9q5_pc^@7IpQ?$B{UtDtMSl*E2JB)R}$=uhM-&z_qqLf6#gfY>ptSP?>6AbiEMD zEeLQ#p*1TSyWKj-`2g~~_~lNjRav!bS_(gx8J;3Oeh`jSMS0~HFn(^VmH7FuTP1i| z&q-x~$T7rv1R{Mt0Z1J_1z}hF9w(**iHL?_l{WQ%P^^3eq~B5{In%#ClXtbT{?kf( z#YW80Wt3gP*(*IWDgc#y#NO*hpf2!2C|KThPkq|Zt*74yopTG8r_4-=6E#50^+Xy< z1%(aGLRi(f%kQ)#2%I$abXR*vD-ylxyaG8T87d1&XWxY_y|UitCG_TisZu`~=!vzkauYr@@qvppc7e(Ir=D&L0lj#ZRhx95vh#kh z-kg_hm3fePM80$OD6bu-xWh${o9M|RUN&xj9_CN;dl)t1836icbc5s_Z31Xf_pfRg zkfh7>$^95Xmvj~44?Y$lH%Tlw0j9n~_g&N8RpzZmnI6dFbdY5EnJ)H(<-l}n0SN=n z_$NCY*T*83grG!V4v)E|_u3B87J%gB@+SaC=c-5MiSyz2YZbVyGd#1-N3AMtXD0Q3 zYubvk^kg0gu$vW#kZA`GcE9-01`H4ut=s%%*=WG?y|I-j8VgVyKfB_?D*th))QG!* z`cTeZ2P85}a7GN6{6AvVmE*I@>0~OzD5_xc9W{M5uGTb`Ql{z?iL1O3lafeo*HO~O zwt5J>@=yw68@j+|!c&R9?taYBoraNrXG*<;cJHv-q8M@uzs`mhLeYuyet7BM7#FCe z_<7tkuvC0l`TXh}k5r1rLCT>iQRl_LZAa@^atiY~*M-R3rX$$3E_`)QAF{AF@ z6l^y)(v|0~!j)O-7z zP99@5#bd$pdC$<}olN=}ugR2A$zE=S0nczCQOPT#!$Du?=9EL)K2iYovl!I&zF3cT zR4e+QKL7KL3Uy-PVYl2k-jAMt7y9dcS3@Um^%2`CA#<_eo-ZSCRg#7I1r&6%Oc*b9 z_6?GioJG~NqP~CB*44^pA#p_C1G)ZQ=9q!=Ka6QV$Kj4POU`6{Q$%dS#3g0yoaDVV z#7JyjbQ`GHsMeBYYJd6lw-&VuJR`^&uJSYO;;3mE?H7=0#JNM2Tq9$D$)Kw!aMvQ| z?)kA3ri}f}j7k{HQ4ljyVe6ta_q5K z$#bYZGBQZ4v4!hU;weqhYJqBD+nE-oavzp6Htqkyu;eCW+S*$kUsECRG{~dQ`JXyf zXI9gmyy%>b&O5XVOUuK5t*h0!Kle+|Dnxw>LQ*<7^rV&1_B|pS=xu{gx(bCu~bwo1fjIs!4Scz$V~>fuwhV5e$cDI zd^yvM$nTLQH%nfRTjbr}Ub1%@2#m)vIW$X<-f6(7{_=*PHiglDOtzwLFVvYnbUhi>&df|<(hI7kfJx>E$l%1Lh zc@Q93g*)4y)rn<)rxef{e{lr2*2foN_)W6cVUUwS8T=mTF=xPfs^w=V^RI{Khwzkp zh3pc17?g7 z1$*5D|4GPfq1fz9tO)RqB$teO)KU<`s#SL#7YIbZLwSaOSXu_KHvDem7gEDa9!lU) zLx8=;zqYv@7(J^{O-G$6nUbS@FZDu}hrJ1(^^x}w-^$R33<-Jd!I_+`tk<`rV#shYnH`(AZz^N=TZlPP zjYOUf8Av{V$P;TUYv{EsMmW4_(dtiFwGtA01mKR|dRoa{EiRm0u7Gi`%&y=vT z7Hx6M{!0G-LG8%3_1L`cIl0TCeHzf-cloQ-bAt}u9vGUySrd>Gau@b zWM7TUi}HiFh42ojLkb9%>{N-W8sGXlNhc)jGHdFc-9UE#NY~WuwSP zyTFU(n<^*7VSsd<9+*U(`|zGM=^W)*O~*WcK)+}4&9nX~$TNenq<} z3tQm_c9b~2pHvqMb?!iivcxA4#(hI3U05VEs0xKnIsz9*q(1VLrs%)6)hy7x8SZ39 ziVx@|O=8#+$A^d@QKktcE+;8cN-aH7U}bd2F7BX* zw@(q0l(aTQOj;(54oK-HcNve=K(~-B6@$(6T_9&#OK;mk&pM?y-!2Ese=IPqWyNYv zDH}S_E}ZguBN&WzASEh2Oi z9fajPptC-_BBO_j3x2%~#cZ~3uIha2#R7jHzvOH}Na4)gzgD@|*$!9;6wf0f^SxBk z^E9y#Xsvhj9`g^+KIh&B+F0jMTqYwzH)KQl$<^eyqeq}uI#c@w@tOau0zGnni3M6Z zElTWA4=dh>GPaOAI0>AVOerF2S_Hq^gD^j7uuQU~>6(0V7)c#=Q>`qY+wxVLQJr+Us8KY80<2Dn!wo~$DnjkjD&P}&+#nV$esKmC(@RLG1f+YDApk$l(b zp)%VjHgz2u&TjH8FP{D3XHsZ?gdKJ@~>d~$9kdQdo zF`?{%xE4nbMm-&=e*jn^)F$ESD|-{Jz9FU757*YU+{f1)3!JuqF}x7ZMh@BPcrMtr z;eske=N268sw9__i@>L2` zxB5v)`AsTNenS_J&l0JB9a3(2bfOivKstGizWqaM?PSlo%(qa#p{bf~5BSdX)R7Xk zMcl^~+{NHCfbeb_(WEJ6C`Z%TZO36~}Zq>oIwoQe& ziB@`)mS8MS>!_wXe8G-G$FX3P#f=DB;BN(F8_#EDpU+C2N8Bdxh-x=E=l*1hq|H`~ zpMV;_eswzN=7_X?qsH-50e}ol{`gAf?xnULWUt`2L ztxs>L!HRfViBh^6^8qQWGxm0beoL}?e{y~e9fIAteEEcb1%x%)DaQIWUer%34rqhA zi?I?clxg8nUkT*rV=g22_g8L%*vN+yqq6E1^3ave)sRIc`WaECXq|ts`Z|a;vFQ)o z-D^(V@u-TEkSdZ(fjOoTIyX1YDiQ`)qUBc-jNGgj3#_C_?7q$McLz`dCp;nf%fkkD zgOh{*B^0lJXR~UGp`5+C+#$;QG*(M#{Yn(Jhyvx`OWskDPQdO!SqdUo0_~D?y9y() znx-eK*{HQG@rE(%=r(zW?ls;G3&Ly6S_y3`9QS#d6J{20Sp(K->jjiYG$1!ERfr9= zl;?iaJR~6vi|EaehM#g$n3~K99d(jg!{=(oUddiwXzqw%4@ib#6Rb6#8#d{?d)AjLWNr48^ z&_m&ZxeKj+bRuVxL;ZSxJYjRjL>_SMJ*zGFTT|oe(6jx?Oqu}2OytnC5Ag2GqZ8Ak;XEv=1*bih5NT$C?umzT1LhWvl$ z2PBXO@kvut4{fHPZx}N|7`^T}zK&RPiK9a-QvN$Q&c0xNaUXJK7B_$hgu`HR|Lp1+tY$d_n zw!-4*i~|bHKHPT*$-;YU@|&Gztr@E1Dy))5*kAu=NnM~7mak&=gSq$5t4NBP+90fd ze}JNN$bzQF$b?yH!0{U;X@^JoS|7g|;s#b40f@FI6PtBUueb`%p@dLvU<52YRTE<+ zy4x-EdF$iR?Bh7p^x%+yYwrE&`{x%St9WE(H1dS7k6^0i{{dI?AW=W2Mh|BA-b%#P z9ge2xm$}LVh<6zWA}^V~1PB|N&5J011?wfZC-IqEvA9+&hGl+~{ZV-~Gc$ZV&Y9M^ zlMVhU@b`n*^zxGo{j%CUN@DOgj@OOImYwdqf5uEp{s_Ir#?sKkSHH2uIN{m0XmfsE+Gyt2;z z<}XLz;CO!75+)Ah{nG4m02eC{4sFF(*pUPs$*=_YO(n?^>d`M_G@4P-ozb_)Uzz6# z7E&3lRZ$-IRj;`Eg__BK><6%aS&c}Er6w%7HcQ&Wsbq?0-2a|El1vV{F)5rZm_H)j zE79O5W|8qizZ20MxqMjAgb07)7xNHLVek^I6nDC^{KUdr8!pchpycDaDc&sBr)a)D zm+AA-zMZh~?f|eCarXg6UGpOQUz?c7%A5cl@!UT*KFrX6aRTIUWev@L!Eic#4zS;- zNliF8tR||;U>RE|Ua}BqpFXNoE#~}4TY=9|a9nNUZ9KXJamuxX(P-v-k$(Fu# zkw!3ym2_0>=i!hR4VWF2CVj+--XdKO$1wXnH3Xa7KbyS&uEaqJ(?Z2Dk-TYeM zqZK&Y9j2>+m=tI?+EISeti2VKHG2P!KnnM#q87Ae2-QMfdv>CbDGBK@(<3a z;H#evr+_S8M{)*V^!N;ArX2Tu*Dee5E-u3d&Q*lrMSZxax(t(5TD;Qd%h6{8ERCQ^3C;UH2-%&F>cu-9m90U zl8uI89F}E&|0r2S{_qrHXm1HeX`hEB)Ak}j^1mP9By`0fDX-8*nnVI$?a7)A>n&V>@?qMdGUNb)y%w7!jKTD_m~?I`GhgH zkt~LP{GpL$v-VfErF`W+f@I6fg}2c~2>Va{+s@QflyyfuG@{au`-ulNf9#&XHUe7{ z@qmuvM-265-+s{oxtlbycBm>`nog~}If7IFa+qfbFf0gGSAY#T>dCG-qu(>%zJCe! zXST?4svQ#n_b@`?yvledv5)X`C!&0;nTydXw9j}6nbQUAm2 zGv?s&*SmC;aSPectuSf^P~hv>E;#-k&?f=vX}G@=ct^|NgQyPQZ}Ml4xg^qL@mA`Y zYK4tLbRSOF@k(mCxX3AN#q|V2av=^>^mBrKb|F_w{i{>S9z54dEi5AIHyXMhu^mKz zTpy-9Fbq^}COgURD{LEkpK{4L(~@|LSNb#yjG_k&B`F|>5)rkYwTl^zpx8a;)S&hy z%CVAcL6@5qz>ZIXv`3993o#0AS3bcpdqeOSFdc0PD0wjyJPd*@CzK`=90eJ#x@!8N zQw*cp!r-iV2xx|()DJu$e@5WbuVQ_k2;=Z1Bl)v@~^P%T?73^)St97G-LKEE}w-Z=OJ+C$`T@&IwTh zBdUvmp#9oVr*A~E=r2!+ok~_yqdN0cQd0Fnk1sjhADU$(fagj#>d-5oVY010!AllUh}IT%%fgRIsS3T?mZD&__AB3-(Ye)Cu$S-1$TI*#t}FzuNNn_$KtFO0@edmSJt6u>74FrnH># zT(V`R$+0R;F{Y!YQD%Z~(jIF-Kc$XzVI8(6c$c`+qzR4JQ)^~*JDS*xnaAmh-n!4~ zv(i4KYz{L{s;NEH+vk6Odr5KuwLiq@KO|opeG^5Uw~;pnPuoTOCY8NntO|x|0f@A~ zLReNIc#Pzt-{^0Oi0maF0&|ZDEOiM0UV9gqIa}D+9J&somVhMPdSIPt7{tx5?)k-! z&01(H@{Ty?W_>wdUKISIylqEog4~cUa9|_rjce4N<#YI(cYL`Y%kG$PU>K!3w*OQDkms(a^3csi)n79eJ=E{ zSK5<1s?l+Z!_q@3!mi7Gagtwsz-!)_}(i~^B`4H z;gT<3!J9}1Z0Pv*g68D^`?J!nev%5U+#0m1B8>5w4Dd{U41T(0UmRK*e}0b7`yB26 z`mN))`GkDTxLQSM#cP;{1KsJ$sRBMmQIneF`~uIHOkca-RdxZ-ovz*bY$Udl-2K8T z+O4&PXY#eR*T?`?x3FjS2#)bjKUKf@p+1}T?qzn3W#UZRB3=wCDZrccE7GqLJI(HqD|2&Gvh zVR%OE04_k$zdJY}Z}>P5&Ymt$Gk8{LEbJKH9j{-%q{>`b+cQi5&447?P#FvK<+EI} zb+xA7BEpSn1SdjjBdtdf1G0tZ0~v`?`@3q5Cn-W z)@cY&91tvE>xeUwk#iS|(x!-Tl9kfTbN#SUUeTyqiya{odqK7B0Y)-kATKIZ1M6k1 zitG*8pEs%4@BvDX3^KX|of=W*tHa};?%%}Tcy~33pVug-5smiQMIq9!N6BQ#wHu6k zMWPfpj$h7Le;7d*KB+o;#@SqtS6LkxlEOMx#mK0jhWzdL1kJH=d zUu&Sk{6C6eGOI1Hb8AxaHuU~c^3S|-c8p)wJLgQ>MVw29o7z~X+S)FYg>K0=2JIo@m+Ooc37b$`crSjDxb~ zIUXO)dzWD}bpykS8=G>H1RnlOz(=T6vsl|zO;t|{)F#JQA1r}SWZ+dJzPSp`d)y9X zk7}dgf10&?tmgE+>6wT!^|$R}cB4>w?uex`BQx(ZJ`r*R zJ=Mhp|7kM8y06@X0iO#X*33l7R-2gT_fw@If1Z6R4bO7gJXo|$_wFe8vyUo*<_cEf z?_GRmEhc}*>`k+>1AF#!Slv@Q)f<(75t|Fzk0izpN`Gx`IhCU*R?bLFi`pS*&`T*u9 ze`J=`3#+VSp2=i1Xe1k`4WZ|l)bfVvMwFHzs9g{N;mM<2&gLxNN>$-k4t*A?tRXo| zW@u*!YUmY%(G<9U;23rFJ_h3a0Z{Q_^$;r0(VKoo+pXQ~=kl4Fq}e@~OSx2=mzySB zkMh+XB2x$@F?6j-_ty%|xNwj*;dBL+f0lOl(EhkqmmL+V`wC7>htd2v3JvPtv!CW&|~WKZoNdAf5zI+ zr&VK{MDTu+Cv-oXA&TcI{OkTZ3||OG0fFYdq?O4^pfTUhCE~%EvMCwbGMl~C68>t& zre`oCCyX`LC}Lo>XTk~&yW?IC0ZG54dE(Znr0hionK6yTV?ip;@weg*cY&e*F((-=6Sc0tlM@C#weN-mm?51=OD7XxK<;X%$_Iqm);r*I>0*dc?!Gfla5})@o_O_C{S_;JsB~kAoh}WQh-f z1l$68&2`l0aiAK|u$&VRax9oGj3+E05vdviVP+AY?9-*M;571RZ5Vx(BTzEeiySII zHzNl_1$Eo)73Pf>^EPT2a^P8KrK%#YL7NM}`CM*9L-hAJDR{E1C*eiE>oiHO?Dbar14v>_ivF5u5j;)Z zRIV#nG{etLU-?Kg1!ZWU7G@_S7Zj!dbug3rqLI0cbTC3rLHV0Ze<=6u{XnvYz1B#J zhQdiFnnDg3N(`Mx=nB=4@9as#u7}U|yxY^&V9Jfp5T#m7`;7blX*hrm=2w1gO)Gkt z7-Rn;d>QxQ--a7Uo^1N^2m;QjaNDqGI*st(ua|tw%%uTcG6cfaG8DS$WbR_*@Ovhp zYSb}cyjmHpiG`&>f7JVUFOOC!#5IN9htgsp4a5Iw*ga{#s8U#o!#Z_;H-raq8;kjF zOU-zOGAPQbv8TeztdEE$#JCKw&F+>$>8~piLgkO*-pWnveg8`lonIS@I4VDaXSzK; z2YZ`T{@3|W=WG%x1ENM(f*FWIATV645B%Ou(8nq_1&x7_fACDP-6#)zLa$tj0dw@U z>ppvMP@_@GbIju!Y!~|J{iED~V%VT@4Azq1b)V(|Wcm$V(Hw~hKXWv(G7Qo(AKGXl zT=10*xp;2z?chTS(C?)?ZR<0d;*I^Z7B3$@9GImBo#l82H7dZ{cxEfK+8}ntc>6dQ z?SsVnfLDVlf6V`4KaAn39~I@2RJ)EdK>J)tpWVB@c^Dl~jE}lA|KFIU_^*)~Ke~MWHrP*g(md0k{tg7*N+HQk? zi`<DY#{$Ty5CLMe6NZ>!$F9ZZFu(I_WZ1w(>%kL0m zkE)HQ4_}2XI0M_1J673SbJ_$w+f-v2LbIJ3%~z1eav8Y89v0^4tP}I-rqyHSi9TgM zDSVKAYD7H)_Ce^e0agpbhJ15^uDbVUHomJ2;qD~IZWu8MPFMH%uk={~nn3!FJeX1t ze>!ibE!9<&?o^Y7=b`tmAtPU!N~V{x*3)eCSA7d)eL^w{wJ!P`I%Ub`aV8=FmP8 zSLhz-Qp@P`gaRnFa9$BbF%)s=I+*Xhd%|`$iK};-> z)zEpR+?Tn;o+>{%xYqtuhItDJEMXvQy&10_^v+vj*5=(Y=P~j7kS~?s7td3Uf7M5t z@=v%-Vnkd`S1)Df#}`1qv?+gtmnE1I|S; z#rjZ|7BK1)jYndyVi%DGKE_)ce*lIBjt84SqMB4Q^=IE^ngGa*T#u1P*qAVt?R4>sv9LE`u%BF-ZSoRr8I z^;oVKu-E)$l>$_cD`M%3&4vW}7Da3b+xH*o((>GX8ps;4xB)@6kMEp(f8p>x1^8EC z3r<<@;16luh;YO_1!v!p9JT#3%iKQ0Wj?RT){1-E376-tPt*VblCoEUD&d*PNOJiih{4TGYMjS5Kd)3y7`=&@@L$mz8Pn^EXYZV?d8WfB0anwbUL9&5gjn zc#8_oD824ww&B;xctRHMN+kW>0(9h$6is9`nyVkl#M^Jbb`%;TxeNFQ!X&nFn32`d zB&gV2dO9pKHa*_SnbXTdoZ7z$->Fe-wg4Bx9{979rNo;AdGK#hDUk%lVjUZRq*vK% zW&XPslsk4pzFQrDe*#UNOL7c_5!3$R$%^ror3G5xDys2tFPY*=x6L?|sGjN=lngZS+NI>uF_ehCfbl`#wrARG{FQSW&L3SL)lJ&HX><*h zeLJd6iw?=tn6&_g%<&Q}j31>BrZQQix?D`&a*AT8sFTbAf0E6ku_u8v@`LiF&(MP_ z^n}tZ6e0XyNHqk&2<~}Wsg?5*xZCfKre#|tGSW<`gM%(FJS?0-*f#xUs5?6DEId%XX=;T93<_X|7Xs? zQ04kwn&)=;A1&4l?)%P9LI_mqAN+QgY3VDP#-n2I5A%Ye#r~X_5k+hwtUX^(*b#|@ z3-O8n$_Vv(q^@1qF%U6?u6^UJCS`w>reGe~o)nPBe*<}zoiw2F3-W68$54G->U+_{ zI^+7hz~{}az2eTDk#orYm#oIv{OoKh^Y5t2I3{$haExD$oh;9tyDJq`kBq z&nJM7S37EfV{`U>pxs%rD7?edsFAq=3*&7qrSSHAOXnsRCM{3yz+jm-6hL`~Qm?5B zK;-7HLuMIl2-*y?e0019uzpe+(Z~_$8ewgrf3?g`*@IaI9V{gZ8f#~(VeNhi(}Z%O5_&osUOU(HpLZZotj3Q);;j}ht@#mmLqZ8J;FHmyBrOmg#_Wr{llDxM?Ecs(ofi-x} zu-Og*s*~{0Ez@Yb{kR6j6im-j$VmE|D^_Jl%7cm9&U3*|XpJ7N*PCRxLN%oOo_aFO z1}z76Va)FbQt8Y2LLP&nuHA{}P*`cRe+kYx-Uyq^c(W&5MEprtQ9cGfGiupQch`s7 zl})#cSHSG=!yxcZ-ldI#$zt6T6y zj@y8I3LtgQ1co9RpTx2(y-O>8e_Cz0rw-~f%U!Jz6;ZR9a-j|k)$OlNZ z^(_moCZa``2iEkfo`lvi#7ewhPgf-2ITe>ZR3aB%DK|xRg1cdSOdzG}+Ry^7%?(cE zc4#%YP00Bm*OsR9&_g1B(?(t`NNc5Wr@a&6mbv$lkgpT60|K1VK9BiOf3G)U{GF+Z zmAg{$nuwH<;vSYX+qBD}8XWgU!UA+W{e}f*1a|>sZ5tfv-AhPx-1QH8|GfFWBaSj? z=k41LorL?59fmqgc~BOqOrHgFz&|IsgRFwFeT7l#-Xqz~fADe`D{Vp`V`eD! zyH29I8powCmBQ+8p63gge@0X<=}#4n;qX}UTNPMLw$pNFSAC3TdppDo>p;ssKQ=Z2 z`jqgh0HcJ3kkgg6mQ~ZCOBb}t^be@l0e=c_uhQK|kH^f(Yq-Lgwen{n)S_f6+C2aN6*dpMLOzMAv?O@Dzw!D8LXhV1 zM>Xx;29<`+|;M)axTk?R6O$4L@( zS}s6r%~zRSd~d;*`&)ZVfv^Nb`&EVABoB9e6~L>`ZZbM|l?Nw3we@Gn9Y!YNoR10` z2!crMB=>gI42*r~D|ct4=#m*eJN=8?N$l!8Z6lWf9ssjB;ianPukYH!tD4q|E3_n8 z?q35i)bJ6{f31h532PBX@?E1}NIZiyC3gu{oD2WIJM@Y3cxf`l;2J&|e=O??wGvv_N9}|JCdV)x*dDkGsFbri{TLe2h^L+JIL*5_Kz88lI^B!EWpo3e|T7WfIeHYRWA-7rg^ZnX?wNjZZ5nZ zYEEslS;!CAPsa;X1ZFOU{7m#)aFCYELAlodwrdMCvupf4XzH8Og&hQkuB9>`kIcDy zSsUiLFG$v-aE@vbdVWrJ74eSSs<>@pLj(UeN8Z#J&2qh|q@_ZH1In47TI_>z4wv#^ zfAOHc(A@(g2IEjNA5$2Rm8x+HOsm~RvZq;T&h$W=J=cN6%VcPzzP%{TBtMiGdZ7ox z;L~0@M1Y`AR`yd*8VPBQgh?k4mJnxh-8n%YY=Nm#g4dl%vt{TPDFoNdG}QJ?gKJ%r z5)ABa`)e5eH!1EI#$;|CqV3d-l_zr8f0EygFE!^gzbAwgI*8p<>Uhl}%mJ!hNyz&n zf3+kn#qt^EOL=~{S9H!1CG^_2E{5n@$~q|6+Ql)>WT=nBXYmbb$cf5DEBu&$9D0-@DKo_XAKSFrcf`7-*N!*4w)5f_!{)^d)QroIn-zMFN$X_T$4IzIQmpw99{aZzA9FF~71A8}Z@I8wC6S{Nk zdf9o0OxhrRtKy<`#3XH2z z;}oA$$Ze_Q2GsTngpRVcGkfY2$K2L<9MRU=>b(I{!~?F5LyH{V;B}Nse?4lIG70yrX*QnO zG8Bwq3uy}v2MfPW0RM=zmDnAp+?Y?0+3@D}2GYQTq;XUx990$hZ)(1cfL7j?eC^_q z_~wF>lFAamzrqE}?qxT|4YwI|?k3fEY58RQ)4BPX4ibGL2D zf?8pXkMeOmLTGzze?YV-7>qNRr6KepAMOJ+%}|g0F~Jnf!z)-CBW=WmsIzdfA@J19!DfFI3DK%G zu7p3&G5r(v$bY2LG&GQF}L)`f2%$iP~gW7GxsgbWQfnj&pMhItRtPoH`=Vu zh9k-MdgMB)e{QFszx}i~d>CinpXm3giK)y|m-_3~;}jjmiWhpybBai62Eh!*lrbv; zUEHnzNKq|?h5rF8T3$Ibp#{hwFuj5vRKofjdKme zUIVS5nnG?Awbqr!2g4Nug?~wlM_L?u3BtCjSjDUDf9hs6Of3ow34M@as;$N3jc4Ff zKVC_WZaoP#tL8aEJda4tFuck|u9 z$L{ZQ5$~?~2kO)^!SEqpdICVE+kengJh1XCLRm{`CvEjg$E&qXk1-CRz`UF^C7IG{ z&0zy@e~wi~`u>Mxklq|_1oG&M^9joc8(1e;sz!se^J_f5v#@()))@&+0%k zIO%eGJ=&Q2IqWdoSXA;2Zqm|uX(6zrbhhy5mU%7pM>K=AWD~1@_FjVG?{W0GaNrMvV4!0 zwb@zumw_PS`U*vN;_T;5qb}dT&&2tqvic`Q11Z)WE7NZ=eMRj+#-v0|mYQ_JISKJs zqNOb6C@n9c>x2ACNC|-|?(mchzjkTN&om}-?!P-3k>`~{C9P^xJK~o{To=mTfD*qE ze~+oI6)i21%c)*J2D+41e-iz@^e=)cGI6qQMlh8~JidT6;Y96JLQdMIiRuxxD0 zSW2=8MAOH3i9BV%e3hL`{-~w$jK8o60Y(qB@XK*T(+uub&o+#@2T;L{KJ<~E$av#8@Mz| zIvl8wIApy#KEvnhAoheIs{ab2ZB?Y%NNiZAO$daM+NY24#BhakR2=jOd)OKxe>aFK ziH(F$TywpxmG7R69z*0^oJ!fiaTU{{%_@+~0y^zwfMD@22&$GKVJY+Xd8VtZudS&F zr;2TRDOpwk>8R=YIj!T6sRGm>xv#F+F2uD@lY)Sc`V-MH+Vi8_IEQ2;n~q7G)R~V1 z2(=lYdd9WdG7dc$zJw#4B83D5f7I-3Pj5dZi;n6^PC7orspr|W^qsuUc($L})4A_} z8Ggfos$_%W4yjSfG|u8%D+y5KeG-!XK$#}&@1+q|L|zsagj}=w&n5!-JXz0&msx(4 zOLXI?C8O$u-%3jKhitH-%C3L;Ib46=x9%9lbG2PaRf1oGbo8W3H z+5PZ!I8&a=g(4}#Ivno!opY*Pa{j(2c^J=e98=sOteHyDj)CU@*M;e5p?F$e6C#rl z#w9qulTZoVdU~?HYe=zP2tf{3IgZoPJHGH+|E$N)gplFU*~?2dWT%;#eW@!F4$Fw+`Hr%k`!F)C^7c@f2x+or=|Q;=6o4o z9+@d!0$Sv~mhz`Vj80?1+sgnCH)__O55WiMP#^9HP(RF_cW89#s>h+^PzotwwA83u zA_|yb!2Mq94i_7<_ux%DUPtt(<|FdJxjpu3=|dL@dXZ@~mYGe`o|h@qCBUIv*UU&f zX%YqZn%~5jUAXQYe^e6Ji%5T=%CJ7vPGF+UyUg2i<6e7VumS#RNWfrBWU%?dA)s)b zK%>={;$;~J8uw!0YTPC2?glLL#$h)ECVqRnnRGw(D$e4xf>|2L&mf297eijlbwGzwZh1^(Q_ zeYCIXe+v6>s_%dtyn_-enAe`*DhH~NC~qjc7y`-_JkPm87@a< z`e8JSTdGy&f13`IYY0F?457(f5OXTdBzcH+bT-LC(OWiQSi^r?NcFrllm^k%WzkC+~dJyf*t4119>(M)E4WA%rb~!FO7pS3GUwu9D^c zf2BaqnRCj>I^e^vKoihD@mbQUSn%xakA|-bRo`Aze+#cHqc^*I!0>Qeu$F~3^K2YI zUx0zhYrfNvd0_ZE>8Ju%my3rjC4sQ1P-4()F_1gBBHQh!8@I>3Td2;Od5HR`Du{B@ z=FJ$Gr^CF9nJDnZpbhVi-fnVht(A*?x&9HXC?bSkbfeK+l`=lY7pUW~98 zM9U4N+VvGaGs!lB7wVLg++}J(p&%3kE&_xAe`P?H)V*_$Dn2IHxi zeh6hw@v{lee;k-rlwBBpJQ& zQ5L|R$>8%%7G7O3Nw_u5=irkc|N+R+|pm6Fg0Lf}ad^QLXC4P#N{FSE*!Ep|b z6>*G_#iM~K$fCUn1CI;k8$yob>GN0P6nb|*9G{%IPd#YfPt#X-&Ex{N_>TeH8R6oF zB*de-w~UW>aE}?FNag77cSq$)fAuP^tEIiYxQg~5nEI!g>XxI?h3On0yAc0kz*VV; zhS9yeF-TAg1=ZUYa{_NOdpxDh?Gtk{;7Absj#4kMuNAg~_-e&CNhRZs5_yNDR5+dP z=Zvk#oHh>u@9<%KSY&$^I6~mA`_Msw0zpXw&1IJjRd2se=qsD$2WJp zfFTUra#Bo7ofjm)y;$Z&n7t##lCTN7wzH?9E?WaEDYxDi@V=B_-NxTDn*pz8iwMcf z&&$F>bYoB}jFbhC0AX|?TVLgewqi27;}p}RVDf3(J7;3+s_C$`Tdn}@Pv+TB0{VA@ zszAU0@Ne6W?v5}sPm)Dge<@>zyJuN=QW2)1_KVckG}wJ@R)=ULrKZ}9U|0zs^ z`&szlv^r22GC7?Xu&>L6lr(n4`3@m65o~5OH?jAJT}s@Le-mqUk~(IJ&a*7D6tq<5 z;^3-_vz}kO;f4iff2`BwE_IdX=6b4TVQ(l*SB;)L^y*`Cf80fWt@%Ie#kk-Hwa6xE`Q68Sv;S%lkb5V$pfZSYXUn*%$n4+z zoPYR@fs9{hCtizmHM+$7W+RpLVP$wFsJ+CiPzpA##5irte?k_{IJQfJtJS*=HjIJ4 z6CYeW6gO9x^J}^?_+3t?Fp?=KmafREq<5sl^2ay~E}bq$CiZaYs~NRtvAhBAK)!hC z0msYSL%0}pgpL!yS!lykM?ch@_0a?Cx>CLm^+qlY&qbrXk1ZaAyBto88ESx8+8`hQ z+xe|>q;#Uve=@=4Y0a5f1zMby)zOFHcDqPvn4N zoA-Mk!fz6^u=90opuyR*?>430`K|L~E81gMBOC$R3j4jQw~jFIv5f;TE*bevX{s{_ z&OpVA*|2G}3dME$uU`jPEzc_q9++k>_)$e8a%~_|fA5}Ec1t-=gO^css4c9YO^b1g zP~}?`^t9+Mgqd1+U5GOufb?_?9NtC)FR#h>!2<;fY!Qm$2e9zx+;*A;87_`hvQ@U1 zWP79myLH_|bYHV&Bz9(PZQRUfXC)3#;JrTBfxm<|H980Q)*0Sw;W-vggENkx2;q==XDHT%b-x-FR=c@|mN(%wqvCCP|g zr}H&vQW7*J7bfrzqY99z5F?0o$fC5R9RSIkfBpK7p55TN+O?a-u}ce!b3&a#U3x2) zt>NaWYMmetn%EquJk>eg?UD6dm==ld84wN03?X)h`@U02kUSVdMaojl(l1M0b!MR7 zS|ZOToZCDqvK#47&`;H`n|&T-5Dg#Atj~wDekuf{7mFuehZCV!8t*Wv3?S_vU+CWChpE9&f4 zx!`o(SsXi{W=TcWi{S`QwOcr-{>!U7cV{iIyweCw>_4WN*Wj}^xwPp_N2AlmhV00V(>`5|rh;lDlA^+(c>U|w!SK&m+he=@%f&!!3U zrXz#>8t6BY<0Yw{80T~>9LwZzCsUPBlU9h@(UC24Y6_AGwEfQhFA;_H%_@Cn-us>s z{vG%%Oq0YH1?nTv_2D(gz$O$Clj;Pbabu;cLFX z{oN9`aeJ4MhvH6prN2~^HE)|DK1@uzg)l>FPe_e7-SI!FjGJ&8fBD)_pDcqSM?N_Y zp4&588&6DpN1a>Rc-2m0=5G|Izb&vb-$`@0Q7|ZdMXbj4(0>WJlV)M6qeu^1N7^xV zvPUvoV`CGJn#zJH@EN|<_YFkN7W_~pVwN?HeE>zPT_B&SlGGF z^vynE0Txt_XM*mBf3~RHJW1w>hmc2$58djz;EhEhpG!klF&^jRY9p-4`yd9)qQ!D7 zL-t~2mWZ=p?NQK@p>#hDyt{3u;UlS(Mu%9ay3AigjKreuFUzs{;i+FqG;VrVwuBeQ z;D+UR^AMMbTjv{bZ4TKi;>$Tg47$E-zU4ixUnFpNrfEU#e@{+(6ivsqt9vd#4RT{1 zGET88wSZ%M@wbOR|BQ*Mk^MvKi}PcoY%SjWKCBsvKTY7YX=FgZGct=4K_o(F*FPQMM!^B9Qh zKlsdN(jxx2*f90ED@(7@PUJTaJlnNUP^HlSe%zM+xurM|i{`eN$|zN4GBkf@?1))2 zf4u65&XNCl(X}&qQVZfaOJZyMkkjHcI_&E*XgK6Le{SV|afxzl&99?{{Y5SX*lKHL z+HY@*mPvD$4#t9^3_B?Qu>+$Vcp@7XJ7>Jcoal?DD}oz(;L*=0td0CK82(~EOOhJK zRjAm=u|hYvR8X0h!o`L8a{ZKKSm~R!Sc>iq?~+oe+dQ8wr&Vz{F=TaoP8KxDG#QmW z(IV3xe{fD%J5B&7oz^XskDd2eeu#YgqIb7hHzn^s{esX{12(rWgdq6pMY}j-D7}H)PRad5r;Rj&jen~lF+^x405q7z zhzgo@vO-YpW^*sRrcQC~22I7vKsGFTQ{_c|e~O);4jSNE3`90VZ0*;RhhT3z0Ju<; zShr(ZSk!%gz>hIGDIFua(nKGfPr41B&A64Nz!P?t#yN!2E?#6?H4Il7qrw606%4#y zESs_~*loW%W4c{drxLT!Y|%_|WTfh!!5{r2^yL-Ft}(}W`q{T(rOMieTp(qvrgv@> ze*{!ZYJ%$u1Tc0PRIzmb)vEkFM-fZmvkn~%LV=)6`E)N9HmYk8*zcv6JS?sEf zwQ~MhkDz$o@$KDHgF(A*_WN=-1P>~;*uK7ioMepyM>e_YlM4E|&3{huc|wFRxOOS5 z-Om(qZ?=D~s4$T=ZI|TGkbEus6ij5ae~Z_>vq;+PZ*xTE)44`vdnZfZUCLsC`A9<2 z*PX20f~lrmL)@ebep3K!z#sV3v`yvxNvnIp(5RW&F-ozMbA&5oj|slh@XoM_$H5#g zT_hjz_fOg;vu5b#ZSd0+7X#s0kz-sOdP@K86Y8~Zj;(8naeO)u1<;)VegwYpfuFnFNj}_6JE71i+2vM2A8R?*yhVu?k?+CSX6X5PJ zmAE7YF|niGV38<3SMLVfr9A=`*Ixlth)F6BhmVoHAB@~flrf7CfC$+qqz`g~o=Cw$ zLYQ)xTd>lW6p$daRcU^FzBG3_e``d3i5yEVV{>8^LEh!*Al@frglPs`-+!!CgI>E- z^wIFVE26RpQFGDCbuWngYqy|m)_B@NvPyB6#Lu)UTSvj=#_RPAIW z%5G$27-aZQKi+j?@`3H4>Gfn@@TYu2d|`Ki=xZ^mp*_a}P|40RMz51_bHp zoXflOFDDwMverAV)yl>4psCV`(n%NH@%M=CkwZ|yYEis0)$lgViU>&#Mt%pyc~1D@VP;cry^rCm}{cf3j`1S5{9c{}Tg@i8rSn@>#!*7Z<1-$=SfZjmk8_)zSTD zEsCLFhp<&UknGLjmUL-$R(m#TNZ^WI5UNJcP&P!XPYmo_hjZw74Z=L~FNe)0l;DJ# z3C6tLDwK8bs(yPHq(_Grp6>Zu}gB2qq z&=XYxk#OA32M3`@WTl!|;XCZDBSy>JmeD=iP9U)=8eCal%7oD;@nv?hwH+V2zq;-w zZ9nPPT0*8jFNp^=e^{kfBlf73D2-??3LVKYLRy&&)8m73%BqIU2E zN>K;(_zI6?3#q^;7NW7TlPOnM#j(^jsrThPBnS`P_Nz;q9)TD}<(VbjjeJ1b7JV^b zlZTbpjOWe#AF0yUp0fvQz#E=(_IuWYA7S{v&Q!gN-2)2Lf6zHM`^@0j`=f~a(zf?+ zcONSI3fZhN6Fh)oU7DbOPV`BIE}EB)w0ikgVe8yJ?qYm3#hy8!z0RTa^U4dMb3Rfk zZ`?fkr(gnIe=Y`!@9x1KU;737Z=zpYR>YUNy+v6Y+S6b@f1UIM;S<7i= zO7e+9RtY?E2$RQ`@J*Vh_2;Wgl#lN7P=6pStO!vKpa0lBNDaxm_et{x(_ znQ1NtB5<}TB`AI|0R6&UGEQ5ROLu;lsj@HL2LuW>k*4xbhUu&&O3?gL@`>yi_qFYj zir$%t`R3(DsJDzV#in__?f@kRI#}5vIJ0gKi@W>J!8#V$y>vS&)KA%jsy!b@4RzR zh=tiWrPktA+BV2PCusWk z&svwbAkMh~hD%4YE~xq9r;uVJqH&v6cOl2ui=^2ue=A2tw?o0V;jAU+F>-xfg?+oU zDH$rH&Xw6$qQ6EL-(uEkjD=7HyOHVvf=-5?SZh#o#EJ zc()xH08$g?S%gMEr+`~$LtTaryTT$>BA4zSybUey5=Hh4yhrP;iivsPW*9eMx=zHV zMQ^BqSKE-=Y|It~SRHNix7OyB3&H+cg9^Ykr&EfJn#gcf9RviD#)@~P=I)~2nTa=Mqw{4qqA*qL=rdqA~f>) zR+y)L(&l@%h!_)(qA6SkHp4`LQzh05=V!P(9?;Ld*ag}1x~LesS`bGVF%Mh`M91P=8{rYU?QVBUDO)y67N`kK7KAPMnfL>K{rW z5?26sg4!ZB5aj5d2d8E;e;*O0S{NXdW(Z?N*bkh2{1+KS^f8;7WHs-6wK0>gl&f_3w>L#9x=THarlp3e}(N>o+l`ANA&r2@C+pIRMDgdg ziavMFL`#*x;*$2_f2=n*CunN=`78{msa&!dM^Rl{Y_H=y;`@QY=;lx==3lAgnm@}w z{zxE7*1Y_~u?&yOwcfHvV&o;UYOq3Iv8`*8AtYhE@uvl1p|h0x=6C5aV`4lU!cHRt zh;c(4Tg<8!PLgK1`xqrAteCBNsI5ZI4@XJUJW1WS&C@QneA7d!0U50bj^(uKfT&3h$T?dLn zM$Gb%ZdBP;f07s5cpPG(Fz`m1TG!{JmGPY8uD!IpdClU)xVo?K%;Em)g{i^#Ub5?>T_K=r+)Ldz|0TY@YZoizrcfhPNb zTyRE{+J@*ArrABqyOcg3x2M5B7fV(`#S0&Xb8u*jf5{YDY^mx2cyp;f$wz(j;+U+o zYt$!iWl(iJ$Ca$vW;v?oQ9McOPYa~#DtDriM~Qy~z{H}?HV3YuRRJXHArOGz4v}r7 zcO&pXAEquHi^O=^r?Wy`UXWQYw>7~8J#XxaYSHMVBCpR zOoP!06{Bntz4>$GauV`5J0#iNE~nfO?T=jjd(QMnFsgxDStyO#b{u6 zLV=7+3}q+kE*CHk-k+Gc`%6NxQ6DFoW~L5*lS$m%`N|Lv?0SKzeP*4zhImpW4x1nhneTu8SVm zB<6wkGP{U%@B2BEngE(ue+*-oyR>1{-phcn+^D(b%qk6NFZ>cDFdRf83$| zGXMF9U?mCPAJob^!u=xh*x`%v=dhu$5CT+8fSrNERzFe+IRXF5=5Y6bl<@8_r?ceYDLq zjH1-s+Z2GC;@p<&i|AaxT0pMEfBw5hG3GP8A!4zpihQ*J;tpGya6!5MUWCo^xdtf9 zOnK#5ybO?c_;xT%mnE?ak&C8S5gH1MN^1`G0*GmEUs& z9vKmUC`C4nwrmoe9-6#QEb3@&x0c$_1HY}M>i_E;ah3gYC{6l52>HQCe-pbAGUe^- z3p?xa(O;4mRMvWQIV`SikQ3$z+;9W5tYa<10Zbq-N7_EAxvI2nthheL@(w+p)!5@N zB;3T}bpMLT8{eAqODga=k*3Pf0EB8sUiMA{%lub1RnQ<$GlgJU=>3ZUb%wvAt5s4z zYDtgMK4U*9rRz&cV=BIsf10%vYC4Hy&TpZU<1+PXzVxO&7ljkJ!LLf&!4e&)KZq7o zt*IK4rXa;PhynTZIb_ZO>YI?o`JpGzBMZ2D|;yk_0sG zO~#d29+5TIsJbvJ6|NeHqGL@C<-nN7v8+o4(huCkI1&|Z z)rdUKrCR?Ko3B?J^;NgsKXe6&f@s_~u4wWIvuM4X3yZdAOO)ZV#j4kGS~}A0uQRG& zkL5&~CKZkxwFDlTX=^ffKVa;+!0}d;kJCI$_$?$mSX zWZt7C|GY~IS=Ck70MRs`ejG%UYEhkT3i z`j!*TD9_YbCx4;J#uxA5x-4ias>)MM&9<+2Yz=zox_QxpBH?tEN``<6$(saX>(fm7EOCyJZ^R`#cz8rqO{3Hu^pbM`$)^{%eVPj@9Wlm=;$vOSx&9_ zHfRUOBd*y8n~iu=AQJP945Ust8Y=u={{sb$=pe>ii+{9J)Zq(9UTK2}+sjd0u4@As z6+QafOx4HbDUy8E6jQZ4@nLA+aeK8G*fo|`pJ?N9Fym>EX6NKCek?ZlLP=uZ!PS1d zDJspgmUJgMJ1bVc>I2`Xa-+ekxmHfh17`hD0hh69>wPN48%qG6>KGV8X^Om-F%Q6? z#QCEsJb##F>HYvJZ-mrGyp!hEt^r3=zcaRy6_2r-u7y74?$1EuKMQm)Ks>?D`Seq# zsZ`kbFf?{q3b9iCe@2;~3nGzD_kZk^as2naz7~ffTlB14vwyY1E^Sq#Ylv_OdoUp{ zP;7rdp&MN6gOs+bIbKVW?KVHZ$Ra30;S%F!%YSFvnQ%|xF}uQ?P?SLYnp>l|IR(1l zgDa{YB-$G1DsaP}=hH5n$$PRDnfy@2)N$TUU7VhtL;)QU)e>Wu<7yUD&;D_fk}4N# z9r(hE0YcP>c1^!Ro$|gR#7`3^5JW|pt!HHm8N`V?E&lgfb7{6L=V^K8#hRO5u1d(VkN^`Yi0ke z(G?>8kOlLg;uKSJ?!!awGo9POiiu?&7>a8A;nkTIIE#b03>#(u`U1Mp-9=t(C2UCp z1mn0uMCZ7o$xJs;4Lsn=^hco z4De!}JN?;0;L4^D{3v>$ErglfG&{>_ zBX>iv2sjyt_4#nXBCP6V8wuA>+QcAbZrYKs(DfJsa;CZBWE&=28+1B@m`QxWK2#PJ zl9Se8y-$DP{mw~q=eoadly7~SfjLpIPOmg0gr|!8Mycjw?pNFaDxx>~3V*=ShUGsEsk$j0NxVSOcHA@tf*%7c|tRbJh7i z+T05csFBY>VIEYlfqz1Kw7}Ks^)wI}EWUc-CDq)NX|t+@G)p}*JeGQDBjH1``3lFjTHN7^bCeWvPk5?z$YoKD&TINKuS<7!0o9F$CUFz6PfYBQY(ty7e(_-)cm;08 zt5o3zSweimGhGk6n5K=?Hj3Q1?mlsZb#6jH==>?BaT>1R6bhe9oq?OSmF*3KaMb6M z4uV{~1B`L&JAbHSec~}-6~3Wo2qX+kWx`yq-qn~?(_$7q0nbRVl1oHNBtW^4Al_;m(1We&x7#(8m03Yj3gE6 z6@99fGKKCRX*(SPh}TBE6^97r(|`;ujUNMP#~i|u<$r_e_4MEtZTX;2BcBher?V|O z)z*Of2hJ|r!ST_geArXQsINX>qEZX&NjuSR8k%ZBR(vX?TpIs`n6fwI)LoAbXF_c| zTKZ4Q1#^g=_@>skH#bv^VhPrKVsy0>Q+qum)(t=^>0@@V22rXhYdV}IDImZ<35o=mlyotQIBWPJ?wTL z%&tZ)p!aH{LzkuW68@LUe>GRS2dRg;HqgrC^4Qe$NLnie(cx=_!^9MdpzSPbP-m^Q zPk#*EG179{@FqYtX5g3mXVO)J+PF0C0D)nuvI5n6g@*xPA-OWgR6ISju#vNaM(Kgw zv^yuFLqg>1_&(XoIE=npP&DwbIc3tyP~qZyG5Wy$UV|xrDq?_&W>JMIl^)z4S_`N$ z9~sH>(2AdpZqVz@a}if~etSXwffRJ~2$- zX$=&b5s#vrw|imhVj`=I?^u9pwlTNe!LsJU$f?&oc!kfL(@nu2o{mp<3=8+*_nrLR%(QJ4G-8F7B_Yrj6v#^3OMSsroQEP$I(&1R--5DyuCOjn!nAg*IvqC3TU%3rU zA32K_lSj{>8JoFH02TEzDm`ne*vQFZwwYb==+L?qt#6o*ssE zHjiG*SD;e+O?|SZUS9J3B@4S{TJNTIeYuZ6HpyGACJ!D)3}s=T?$Bnj^FZz>a-qM9 z0Y{5gMHqlR1KD;f;HAb(S*;xOP6=jiWMqj0LHh6nz< zGuhpG?%?@oW7BKK!x|?cT$;@Xf3=CZ;62DW3lqyR#D86{c6BYsn9@(2 zfK39zDE!z#3@9o$+Zf%L0@Y^ie9i=H(n-o6a#>GG@u%nuOy%P!GWs2AsFUo07`|;& zYb8pZqr5}`Zx^>U5B86(lBFKQXBbvc(ygZoDiL@9PFw|6i+^|E9E6G(EE>AE{NYVR?Ou;Yw8w}|{?qjl#MiXT<*0iAE2(|h z`+k3BT#|FS^pqS>_hY_-CWtWRXA7SHwrS1X3j`sukw(ZB$o!tyYgj&Ru_LO57h+n} zyN481^VEsO(8hMf0F-SEXz22yY{~WAoB?LGa0ft5?^wKJ0)M9^V#1HWBz2qV?6n$? zmxy;y0$N~#Hd2KR?jxy`}Sw9O%Zuc_iTRJ9&DW#uj?$5G*4QhjS=$PhT(&a5%N-oW{3qj)sserd4VD9m3iPDoUxIcZA zYZvsto>wY)atK63`r~;*__pBchc6m7kAv3e$OVkVT?1ZkO-;GQWJNJjKo1M;HXPQ( zF&zvV)qjbpD+Ph`H_yE^Opb2)!!X}-n#h&%UHq^_Fzvb&eH@}4tqQI+v~5pPWP+^S ziJoimiZj^({ZpkLNt_%U)q3FP_ z=GZ}y)GBj7QHowszfnT6-?3kbdt)JlcvOYOi({TnrGs2Mx3Mj*L>2Ry!RrWn-*A3; zJg!~Co39+Y7g{budo(^mYrjB75pm@(Z*dpbVHhGGo03&j;?>d^kO8XdX3+W%P0;{1 zLVpSmkWe{1WnUTqPD?z?ewSOd#4lfrIxT3V{#SrJ^+%k|{4DrGuhNL~yng%uuYHFOHm3oa2;Qru}}nLD@oe zq)mZqGiYBYwxGH9ZXg9*AIzoP0|`w*1%JsxNL)--r0E(u$m3$YjGhZB8x&3Ty8ww z1L`p=A!qM6;I4tiTZA*RfLr|>yJ!b-&!nHMR9=`QOrLZqrd|<_x~-Vb*o>KDm49UP zdw(zp8_Oh4>5XCWdCVcf3B}kM2oYK>pF$Bm-SYFJIi)@>y;I4|5+Jevgi>S1DL*<{ z=xvap|NHCIRX=+K$|ti`HE4)a+5eN14|lw2yf?ZZrYlrVgHK|xs7okIJ@TY~L16Froi1;se_@2Uz45D*3*ZZe8GsiHmZIKcwyot--#^b^q8d<0qxP_Sybg zx90Y{*n+a1s@GeQFo=+XCEwKh+6a58;XC@>)VR|XRV$Gh3pp49%{0G*^-*94PBpLzB6 z$;mKuofQti2w|kho|S71r^G@cXc7`iuG}O6JR;9qAg=TyPQXk&QKl~BdA~yea9zZ<&mZEb`2|1V{Eav;1f5U*hlLm>_2+Bd z4wm}z=w}ql)F`Jlg>96MRlaz60n&0fGqQKU72I45@AvsGw|@keD=t>$uV!7aI~;%j zy|*OiUWe)R5a^Zjr)rVC%n#_+Lqa%zFDuO$jST*(mF+0+dU}3h%k^-kboOuvO8FlQ zbgb9TGmJCMC+4P5xQ;W#>a~yaEexC)$h7d7|K(qhRS1%rC+MkeuDHr=+Eb;JEg|4d zZdh8cnE)-%u73rOeKO?Nhxf1pC!{CSb8F+6|2O|?inu0_?HF;{M>xKPj1Z;T904I+ z^n+*KQzu42Ed{%^2OSoH;HyuxC%i%S!K{V+4(@DmI1tx=CIregKQ6@Jzdjz2HDRT5 z+B;SI2>WhAhS>P0lR=@!@W`tM#VBamjb+BKIpoY*RDbPe7l3^5crWYSO1PL;0i6)X zWg55Z_~^O01iPsWWBGxsZj}vbTv5F?peGI&%5&pZ-f_-G_;}Zk^}x=o`U`uL4(y`u zP!gZTw!~8F>ue6tRX<-0<&C{HS$GaUh$51-mjz@DtgDdogZyE?UuX79)Z^B1n~fuU zV#v8M-+y|;F=Sgs|3bp2!YMm-wJgL+mJK;o&3ge;TidET-MFey;OX&KQX7^rAC(hd z`TPe~4comM;~%|oq4*~C^>h^}zdZ-m>?s(%Iq2g!m*cCSgIYJwFtIzyijNEQjg zM&s&PayleEd>{SX;e+w@#D5V0%aNhuo44>6C6jBQ=AiNTg%OLB_H00mbG%L$)ka;c1ccYoJ-*F}Zs z*q+b@h7Y8~>(#)_mj<})v)0(%gJ^|ac>x^tPV5hd<9CmcG+I4dsx!yVn_-^K8Q^Fk z0|M8-8$3M414?&!exvDE?ZPHfZKQ&LN(-lXVcZ4761yN7lytQAyG1KXdj-R2CG+!5 zZJGWLT0zPP#8Io~4p3kI4u3Qx_6YB2`gH&Z@u4Q<7Bh-V(eAZ2U#THAp=ay~hZz)o zLyA|lJ;O%`Y(;`Y*_!<Kbw>G%Yp3 za{7~H(oI(1LatnoNmqqLZ%Ym>R4~nLFuEc_?u^U5!96~Mu79Cl9nSFq)oK#JBKIg3$mD4UksL09E}^J^fvhL$ zE5#RtS1wlQU_)6NR_#?L9Q6YB5giL<(7Igae;DKB4)Wr?F(Ex*aTBVUjiH}k{d^aT z?r?q!3vncgkGKpc-a>KwiV>;H*Ab%{vGC&(!=SGPbU=ZDvir93hA%FYD3FZ3^@p6kJs_@~s?nVjzhV-32tw^D7I?J%4b6@a#F7OYSD;zXzmGx)w zLYII#o8gEBoJvfD83OgqB5pwo8VlIsVZRD7T zuc4+GLj3@jDg!%I=~2;a8&biHZD<>@tqb`@Qrtf5Hjcg|U0)r`g3q^?g6T+pPeGuV zm4B{lgL|`qW2VM%u4<#%)`Y?fX@q5_P1RcS-@r78fc|xTAGlZY;>l#t?#Yd{i~ zD&E-E`u{M|T4Z7z^NdfKjpVQyFl_8vH*Ry~^E?jSKRmV-T{jG!{*D6BnIf^GTz|}n znhMzkl}!@}xgcjBL>nKT+FVkNX4foqpl3@};C}`-3P(r5^)piB#eBe;Yyq^085rPB zQ-UH)lgBmboj0-=z&JBSs52`iQ$?T_kN%<9F^D0$QQ1_0Wgyilhi(RY>Y5>(6Pnm+ zhN0YvB5-*`jK8xLj%nC{f?~4TFMr=fBGH5Y)LA-@W~*KXH8j7fnX!C{5GLa}ye`1$ zKbdleX=q6Y>8Gsk9f4_%LXM%xV1d#WMke30iZ9EZ@6dK8(yTLcTmIlpNgZM0e<`g6@K?t&s&xNd_#=J<0Ruzx>F6)345 zm(l0hNh?%s>O&C1djSU2(;4!%fqR>2wo*ZXU(bG5m?a7rSUi1W%n~uX9clFgO=kk_ z1Z?t-#82+LNkXM7$)1Leegk&@S#{8DmEQB+=}fU)l4TWwtl(W2X}J?^2))bJb%qH0 ze#t*pdSkH7J8_eP*dd^9?0+c(uq?~80G%0ovdUIfgh4Z~4OKPqLG3-~DS{U@&tn|G zNc(ry2{>G2lk$jM4Rus!4|~2wgHb0~j`-N}%r=5;{A0o_BaBLfSZtLhl(v-H6NYmMlQ@@I|@HlyZgI<+OM#4D1V($D=0)LwfIKX zw`rJ4Ir@a6%sHesek&-H$<&)*pbnVF4FdW423nwP;kdj@rRj zKnrReBhY|^Vv7jK_44_WmfZ>#%b!y8MU&D-r@5OD4?&H6zkeq~lcrDHa9~nHFa07^ zp6*iWcYV+J5@i)Q^`x;j^vdP5=jr^Q2Pjd2a!nngnKSFuqlGVz7-atC_xbI`iCM?4 z%n#45u-J?1j>Z<|S$&x*;O`Hj83tvupM|};XN0lN4f~zb;W4$BgPN~*FLOv9u)|8s z3)7kp`?EIL@qZZZnRAnugsT4;9%It|`8ZO9$a_hg)}Sm{n@tm^8AE4}K_3~Y3-PxQ zRW{St7@3M4RCmohID0Rrg3PK2gfnnNKlCfS6RLez*>ME1Sl56@4UUBbos?tE9Zy%@ zG*@8^LNKa?_`xi2-Rn{jdjvBWaD~EX)iEvzLAZ3XU4J;MWlDNXMN`>EjOOI*4>cCm zHQ@oNhkqUjoylyjD4;=vB&945(D~8EuZ@lE%Wj`|?fum^HOly71rKtY>aAmOm*D42 za+fp5gS(d2O<0?C+)&Vx@BE!pxp%`ondOQ&l-M$K5bP=eu4~ud)qn#aAMO60fqX=n zH@?KSk$<`IVUkfo4EgqExUEEu;uyya*MBJ8B zgOJSI(`I;V2u(3#;WFW=2{%-@nvX50L4(rWw0{PG!lD_3KU`NBznzBuuC%rl>Ir^q z9-VSWb)iDSg7zT*WN~eAGwQ;%A-)`z9o%nZ_)=A0Dz3usDjJrTX66UV8}%#IB4k)w zR=mtD%Y~jAs%;c`lSTzdCOw`-`j$N99PI=X@uUAJzsuYO>e~naILT)csNZO zcz=RySX5@6t~2obI_wxBC;WU=^PUpq&&F@bo+H*4;pz%QfM)lME;;}6F@C$Bv5(fj zYXsnShmmYn5p6i@k~&XCviq3(wmH1u8bP1ebIcgj&68~MVj>)Um@gfGAY))@gyReL z?OMn2Z3^rTRT4|-mft=5;p#jgeXJoNx@IDF4 zdSRujKK7;k*_nkpA8V4lBEfbMck}E!d43%Z6YX>z_^4%5HgH|1Y@YGRe}E_^Tz^+x zrqBtX?p%kWC$)**R<*&DM8RA_drKc(h49M#`Mah|M~5>oLW8zLN>9WxHE?~XEk?#_ z@0DSGmt(_obs$G%gtSE(uv3nO=AT#mMcbEt{d<47%Ha5qfDcQYh4svRm!&{S(y{?Q zEFl$)Mg0g6KcYk{uq{EHPRIVbuz#)Rd%|rhE@gE#AA~Ek!K$qyRep?dnAN)rQ|75n>VFsJ<@Bt% zAit5ArP@z!8J%AA>G$X`AX;lmhmMw~dX&iN+XiRtT6;^)_s_Zl1O+d*KBG3x^L80K zqj4s_hve>vGKgTxCK={rxF8|_iRiYtt}2xMTv+3hiq@Ox6>M-8ebS<}%!$82Bknzz zIy#yYRHBAIy9%&aMv{JgI)5?EnC%dS{E3RXlXTDc=o_k@yZnjD+$Jjw*^U4)4TUD9 zaHh;SY)vpZ63_PnqN%H~5Yu>W5rbw7CT z-G2$<9p9boGR}>#j(^WQ#PR-^5Q;dTbzHZLFCJqWLZFp;)^+>Bv$vUCwh^KO3Y_i& zaHOh@C-t1nG8O>#J%*t5y)%}m&J?NZKTzZ5g3>Pi+j0gytn_Ual$zRC?s+i7Zj{u$vBKYhcu-diwAWpX^o&O%Oicg}Xp>UjHjT23O^rW=2ZU)<4nyYhO z^G_m7pntwdvH@wsoQH7_CBfwUQjeh3k%TQl^MI2D#}RuSQ=3SRZ}QIK5e1o{Gn#1c zk}DGpMEKP{z#;y?&YzOm$CJT&BytapE%{aV3x9_Ino^&Eb<3C}l=MplE({^U_$Nw^ z&cA>$=jgm_TeUgkYh)2;WtIs+`zkZxz{N6DyCesdKOwWg8PJn9<@?AA`0Ra&l7R!_ zIA+O}rPo}>rD#0cKM+Ny`AO*@3|-Z8YrWujf6Wh{&DGgY*xNY82H-O7l)J}J7UdVe ztbeeyq7HZ?nTi>z9|7rgT*rWN|FxcDRJ|Z-5rw^Lss>rL^P!2%B8w<=r2zk3VZi#A z0X7@yZ^zo~>zo7EH{#T%nT`F={|3e99yIx~PM$9{s=&UKl@A$#qi%e?qte--FiocA$wb4Y2#7EbE<4Q^e%CW;SR7WAUE&|f#E!`sNV6vB>& zY0ywK=D^k34mKr3r-$KAF6_ux%v3(=l}8&Y}NHSRkYi#PcE}{j&)QUqut&X)V7T#JkO09NZ z9|FyX>6)5Zig^QX`2YVn@KdvAFe2o5D0CR0+Oa|!WcWd&jL;7UTPg%TUOecioi~Tt zW)<^izS=t12cR+2Dj~EWO~Rtms(&58%*$L4KzH!(!yl^&j4u%isjEpzaKp=C@KH%r zj|TC_w&LwNOqxBIb^V4SP|ZujW}s`t9oB7g;Hf^rXn!ucsei)E zs-_+cNbOv>tapqkYmqOVo;sOep|l9-h0Li9s|Ef_btE!`@9%G0T=S1JewB3+#)B)UPW*)Mr!NmHe zT4?%3C8VI5`+C^>EfU|!Aq7+-3L&d#YwB6!C~SnjO( z`Z$U7SOD90wpM6#Lx%cD+Ojn}aFx7yG~*_PbVz?f7j@)3^H~LFYvNE{Sbe}lDqJ1L z^$Bj<)fKJ5ItGBJ!j$8Vc_>!AMP}B)S{_dY5jQgOy{nFecYmZfAL+o?$6d>77p5mq zkG6E2rp0Ey5I{Z%w8a2#&L*f$8tLp^b`0ueWy7uVoiX5dKp02pB_my{xPtHH z{>GcO_5-!n)mqE?crjO8A|OOoCN-q@zbqTjN{D&GYPa79A^P6*3%Goql)&qdJO_Hl zb4{%z1fKrZL4RqeyVRa>{uryPC@c5&ZeoT@trBqM8_21L1OX^4e;K(s#!SSB^i7!x!M65m?3dSPy@0EOl|;NrhdQiFuh6U|OuagnBs~JNsj=a^y*}j+1l57? zCoHzH<$sUupE0_S0yp7EiR1;5%t_`fU()sXcG?o=VAhKGg@ zp&7CWl0`yo<4)VOv!>0(Ote)na@BicMMY=^e9(hh1{hqH11fKcPGlh;>|A-&$I1o@ zjzW@27$>b*T7ezTjpFI)&*DSP{v5PwpMZg#0)Oa`J#0hrJzzSRLU$Isx$zlCAaEq^ z3{OSK2~8>r^Sx614@_ajo==a*AXr7`!{2JUDu8{7;I7dD)#aL9HOF&)kyQP9aK!I9 zc-vgQ&8zy8yZDX;-Lka)X|or}Iu87& zk$;hUII@dM&fJkGYmzy=TEU?VvzIA%+nY0d@X1=>7y|d}kin+6SwiORigE#AD70hA zs#>NveBevhOiZ`V*(+zg`tao%C94YB`x5?KNRuAPJ@y~+rJwbkEx?3mX6^GCQu8oj z_bFW(Zg*`GvaP<);=QZFTI~7mw&SeZ1Y7b4frc`>}L~yM_XFt2}UY&(j+84 z-5(@Pog@nr3$k_A}@y>v1>tE0=){E4_j;HPNzR(BC zN4jO_`VghV}Hk} zGO=m4ERt(-Ou9KwN@GV;zdj7C%`X8Bd-25yurO%l>&6&r9sXOl+PU*Q6Y$SRPn>F* zO*Jpr<%Dwd+QV`2okR9-lBo8iTySfws=R&Mb&4Ig?Vs+y4HQxvA?UT}Qz$j$Q6J7r zO{G^zJt8^S)yYL;bi@mg8_w|`cf@|U@ig89gKVd5jnrRO;R4zY zHC)ZR93-QUK)gw_rf{RVqQ+w7GzyVEDNSjXfya7?jzIG^Q{8#Be1CS@Q*q%AJ=ms) zZ0>U7{H<#ne|n6S=PwT5b$!bNXJfOG>db7|a#4fy&t64Q4UkcWAGClO<$w9*>@T-# zUY{2f-WWP7%RSnCNJJ^lEB#TdowKVEFIp*A7XKL=Ms0gNfKGNIc9%`KZ&&rHTzoc8 zV$4Q~z0h_X(~N?ceE)3`Aoe_hmk)O%>*oA#mgr6(>+md z-+5Rs7VYg-?dQyp#ch3+?teZYdgNawLkvOa#iZUfi4*XG0JucZlTzR~1Ra}X^~&!G zj4-L;?=LrIyJh+fP_&C-ZlksX#-ZkOZ*Qh(cO|WXgvwApypAy+s$1oOzS+0Hj;^nq zH>?0|kFxE8iz66EoqzdZ@LY&HK28zLfb;AgRsh3=Ygbm!4zwt#6)+Ea4*_VZf@4QQ zq!S&lsh$u)+|mmXxk!NT3?k>hO!AJ2`gI76q%`GEJ8{`)q9lz5CEKkf9(H{0F3}Q2 zLym8VQ{6rG;kPAi8nb!UAdGAEvj!3U0Zud=c6JBAuF>oV%YPd@ww7A2LfnFjalvDm zUfcJU!49}GuHPM{goZ{WGIs@d46ZNG9L{_=iFeYvsb24On4eyd-h3H=3U+w;%`bUv zqumO8#PlGdnHxeW_O9#Tnw*{DI}l9?JL# z>W~~X3uqF(^+XZ3!WV=6WPcy!oR>J*G)x{!;)xzquc2$n zd_0KoCe%CH8hsxks+yllONlt3xHvP}`jfjEs>@C9z$5+|S|fJTkVt%#2$*>N=a%seT9uGNs4leFja<>ZClYe;jp>by=`|tUr}sR9PJSq}eb{bsEvRH% z^u@6`&_&l&802NpC7n;Ko$b-i?U1CD<)!Yf-mkKx$)=~!+GM;Ry{}mNJi->#%!#Q@hZ`%=f%RYn z8Klp;_O=1G9azyIlXIofvyg($!8?y)jYJZq{?pn{@?D`GG5cOZPkN<9QEz6u-81WX zV}Fb1x2yLKq;mw|TuZsHmng_i&zhkN6{n|W12z}s$9I=+_}a`dxz$Jt2chLMxdkiQ zxre1}>sYO~rCqeLBbZsjw6DtBUVqrl4|?^30CHSgJ`evwJ$8|eOQGm+N_(#*`N2I4| zF2|Tgm4QGMt@Op%KsLaSB~e{Q+Z-H2)2{0i!`YbfJ~4z zQr4grlS{!|ZfUGqBw*-PL%4zFjT8--5WaWBO>oxHiGL2b zbiVv#$N!6Kj|QLziU4%_{HbHQWY3g&z=A1lAbjYG3PzO#;?`M;s$JVi-1G$|_8K5uxZ^ zg+=xvbjE|~SSuh|`Dr5;YZn=lSbz3azIw45=*y6NB{y&~x5zf^b_=@zD;&^zGXO)D zO&!3$T~qCtV_&(ka^#341PJXML^OCh@pXy!bh%~W!0lWf)hiCGu0jQE1|E+0h3`7?h(1comZFO{G(%V31>;0T7RZGRUaDx*;1 z5r9!1iVXqT`yx#1CHNC8TM$SpbEP1PRb?>^he13p;=TnL{K_U?v>m_uA(F`bbNzT} zr{4sAiWnflk)vY$^6gvhcP`s8**NR+KGK3g7DcBfKnx2#*ccK*H#_=FxZZEFohaIF zAbs*b*Ut?fM~4cNlZ_c8(SKS4A5q#jrcgl6(HGO`nrDL@XIk^kX7h%=;%JwSCcgP# zj93sN7L79`z{cd^qcT$h#9OyW7^$DJ6T55fo}4dHN3+I70e=8JA3y$Cu9Cn0 z501MiB09&Tb?X$D+b@wHj7{CBgu5tkb;w)Mnq@JzI=Y0ll}p)Js8Mf;qC3ABy1g*a zZIPF^L3=*3n+yf`xC$*n38H(8Sx$D;qztK8eLW5eA-HM*5+9s@FMFk6j4#<_z*c}t ze0aG!b~R!cIJ?-DI)A(Cm{OV$amS47urRQdK?4xtrAC?tAb-uf+=j?KacrPZ2f@mr2;+k>buj?y>U+KDbE#=kBPRP-47n$_M|BcUmL?@6~@*_9Sm zcbT*yhjFT`V1K_Q!xx*0{euk&pDhYCij0yqv%`Z>oH+4rdXwhX!WYn3xb>3xI ztmpIv8cRxS^&w(C1z*;}0VW2>Roq1A)M9V=x(VTkN`LtaevJnFT#A#LDw^!hMi_@Y zM9Sp35j1@4pvPzfIMHFO7UR^K>%-k`ahY~*;A@Ox{hse(LREPEQsRi{^U-p*mK(}J z!XpixeJ_*5SK5MiY&k`*sg&r*{-m%Y?|J@3; zDC|`Mlz+4H;U)Q?20Xp`hJ#k7HC1I&0R8t2C4GPANI#aLkuQ=2`WP*k4i=8>sczP} zrvS#FR)$pO>=rijHpEwZ9}3(|z%dE~#`eb?Y^7F%Ip?a=eh;PuBQwk`ge_X42Q_f1g;2OG~d(s9q3- zmv2bU&tp^+l)9!+g(S9yxvHH5SPvbOO=(>WOC^~NL6Z|wZTex-zhItMh>yBYEERVIm3&m1CF z-e_e{37a$RDatj#!*x4H_pUw^Z1v8x5QNUvtkpVtZnWEM-epV()%)+v>MLN9ld%wQ zje;mzH`!qde>-5sTH6qK(6H2R%;{3(CYS<;Vr{dAI1Q$p&z66%>&JK|7@f-Ul`=dB+{y9&fuf>@4V3oA z^AaXswIj1(%ay*}I&lae&dwGaa#LqBCCILlac)|Dp_|fj<0~V9Lz?^lfAe|Y#SOB^ zOioze$Bp#|i}Yg!LA9%KSoO9+0`@E{djct?70n_!g2GCJVXaOP77*6P`kkmO7(J&9S0V%& zsJt%#hE#6_W$8REP7->qp=)ghaq;=ue`@KbxFGjOn^h3I;#1nzg2SwOWxRhHbV_ca z!E;1DS9>(-P|mr^>N%Xms4Fu2iYUK!m!U5!?g<54K;4qZ-<{0Y%#{sHTINryfqK5l ztjEmY8jp>4xRUI+Y)ko1jnqmHko9mxj(%Q)`OFH=8@>YdCJpX|O;{4%pivy|bedFC z0}TsX80lcp+$*2~N}yb80H)-?Ip!VP~8-pAXrqyZ}^ z-szUfa(cx^Qq56!VMPI20u|FAPq$uY3LUW%r-QG9M(^3*K8<>~xybT`*( zw94oE>PxPJI?BMT$LoK&>RQR!aM7ER8^xrm^4K+_gvm$m*Y-(z94S?K)wUo2Yxfk) zy5hLxk?9{W-J@f*S$b-qk}~xq3ooczp$SmZfE?yKUge2(QGpePkTY4YE_CHDpEgqVaiS{NI!3 z8f*FejsNvv3w_92B%F}REJF~)=bnG@+745f(L{kd0#gu1132d8M6;LlFEj4R0Qoyv5i=C-(#Y2Zy7LNkjQ^~Kuv2IP)mZ)<|`VffSvN= z&&7cUaR*{{`!2I7q#1e#B0Zh-_E}dAV-Cy(e)BlTwU0B{9kdM(telvw-FFzwtk#-0>_WLo zJ2F7Z>FoaMQ0jJAhKZ0}3vcY&X`rq!uUwXdConl1@S_DbpKIg**KF~ZLb{(F{0SsS z^#_0BagmbyC(#a)Z>pCd13_fwJ0Kjw9n2gd+;5!0u`~EPfKdmq89YtP&;-?ncwb2c zfjpT*j$HjBWh9RG@#$j{VliM-`J=u_Y8ch!Hku?WgEmx>1T5oEp5#D zX$~#!3HZ_ZYf5lps*^>CLc3LHoe4%Qwu0myzHERUO$A#&!b13gcmVV85b`qSMIu z0`eH*QrpdM8s+jxAkNXPI93F+2O)L=m>s!TOZibWDK#_xwr-Z8UQN?#I<1f_m4^lv z;h#SISb1-G>sRH(Mj&bGGoWTwYs-K2ZNJ-WYfDT2YCD(m=ya0FTlut)i8D?fVMBxE zJo*SYV3CVPe4G7`T|^#|`iuw8w3J$1Q7e1;6r%FWZ^aD-VYI~$2PsSkL~?JTIsO?d zU%dm~T_E>u99z3S^N0tzFs&s<^KVg9p1QHqx`P`7b?6@ z$21Ul=~TFb5Zo5a8}_nLl9G;>fy}iM@*=Id*m+Jz$4BqbME-nY!}E3hCJu(4!y4;+ zpGu`YUykwEk*An|uHG#k1yr>9(03UEjE}DOfOIjt~tcP@X zV+h&VV8(~e9?k^j)Reca&F?TA8kXi*i1Aqg8Kfj% zie5?2W_QUG#MghJrfKMm?VU4Hwp=r_fjY+t@4X|WO^XJAHOrhlVxasD0E_Z&Mo0ql z7MCpXV;D3pRu(ZQIWi?tAh*ha^UGAc!*OA8EPe{%9g;MZ(s=yz$e=vXpxrFUxOpoi zs2V_)?euvU>2ev+me<;F8w0$So$Oq56Cc8d?>%|ju~luZWtZh;`77a}>x+g{T74$}N1 z#SJNQW7~1We^U~i+^)s>jAw6%!kWfooXhnn2VqYwH9SM2QRLq#FW%!Qax9aqpxmmF z{D)lk_ym6{AGOz(V$-%zy5QT(wOYlSv9*&3KC8lee&v9=T_~PH_Uw9!s>)URH*;)C}@SH9wg$o>gK$Ba5R@|ccg!j?0Q!f8R4TGT>3dNCsyF>4Wlx`%~336~Xr`-t^dGt|Wd*4byoOfbs8JrZVNac2i|I zU<#;Ol*w7HW0y+;8fyQ%3eU-sFqjE1E=efUt<30QUXq9n0=TkKSt~GrBxjxV!h!kJ za`=Chasxu@E@Ea(s4rArOm;hT0?$wF!bfrB7cZ5z>zPEfEPE2sJ!#;>!-*g??FTS0 zB1AC0glK?z&>@K(-(RW->V!soS8sMY|A2nT#k7Ap1EIa7Ral4WD-Jmo2*6l4LehpABWR<}*sBj!gsQ(t|P8SnD6HNBGgKS1!`Xh}wVJ zU$6EtW;geO8Yfb z(Zyk#CCK}yAwqu}-Xm$g<Mmq(;&w88af^cdt zN&`A#r{?zkHX%sHL~i{ZgUcL2SH(1^#MyV5HUJ48+g&Piv6ciI`2Byvr`PMYUefW* zo5*%meOeO0S;wR_o>MHIc+fyr1YVs9Evv)ty?n>lP9bcM(JZ^Ei~WNdq<0rS$ruQ( zCTlrO&}CF$Dq6g^V4MicI;H*ys5>%qdC{JXZtAN!j($DCi*5ak5b_OG6w?6xwc2Uh zb*ADssEu#&yNw)ggJ4L@0AE@<1-)wOm53@cFtO06)zQeo&6{mkU07tgX-De~{)l;%L zISbnEH8rCTiuTaj7R(fb?mk~s#V}vFx-5)}n;65^AiTpOAjFnH#gZz>s^qZEiuUAv zr|6u^u&!?OOwB&uNE#B$2D^qu4Pm&SK6`H8Al&FD8cr$kMEuywD7+c3X-D+SbQbt~ zYH>71$fWJQX=)vZ{9xDB2G zH0Spd0A|A8;!E*S=B~@yMT&{j^gkWoa&^8LH-J2g@Q_YY=9_C@%imsG(arGm+iRuF zyhm+0UKiz7KYMg!az{DQV?zG z3j=}=7^R7rT^1q`3qAwF8Z|GZty=cGs70xrO!<3&eCHGUPZn&*`r6VXa4*6vHR>y% z3j%GJ4sw6uJInlmLdAVu7%fD@+WD;Wvdd=mT|I((Gget%kbyPyJ!)SP+NoYQH+?Hb9#{ zl`aX`gQI z$i^lzf0}xQ)YT-#K9Yc5J|GYUMdmfJMP=34A6Zv}iyC@pw!(iLucRpl%xX1PLE|qf zRr^Tx*CBAf&Vs_?Sbz8_!ZI-b(WTdv_iTM8qDTxa#~8cP)Zjd1k0aQ%O- z;Xh23F3We+-6;HEFQNLI;QG$Nf9|`zHf`hxQ*)Kw*Hdch!Eud(f-=~D^NDfXL4m$JIsbwQQ7@U~=^ zPUlq+mFcVdh%@Hyeg-pRsNok1La%>wv33MA&zv-82tQpP?F~BAXZ01nc6dO+&=cu+ z%-zk4zH!%?WAPWJl$aE*=ozF9_239VTZi5e~$XN~RQSCqs7yZDLM$Jt#65+|^+E&QZCdV z2~0mxZYF>ajKg)ctOu&X-_=2r*Sv<6^KsL7jmNH#KEUd~4oP_{Q>%XvZ{aY{k9N{3 z))?}O`U0ix-d=99ApS^?C(`e#b>^Bo1Noq`)dJ#c1Jhfl;=s1#-4IW0oSM2+w^0IY zr~Bp`>mUkh&^;-c9@Dbz70S1YCa@<*yLZn~Hje(e5tha;*2>Kk*FeWWTt*|Kp#p_U z9=fN}Ok&6U34hEm1Gax6?&?hR3-h%)t%v1tm(wG~(tAbqkb9iZ@Rw|g)>&KWMrquWL6|rKI@ZkzUV$XljPFTe`**C8O3G|o* zIWPO|91I`0a5 zAJ1nv_8$TpS&OuZD@f{iJAu# z+$W8=*jsG*;FfNO*;U(y6_d^IZN3-DhZg4g$2{+7Q-xB z+;*ddn3@Dhf_9sOKvO?@Y{W9>n`SF=AP5|F@E0kNgpPkglu8REPPn!AL;H@oARXH+ zNQZ>n)ah&kXQP#k9>5=gj{7#<0y}I9qABQwj4%3K2>>f)hTc}*5Pb+YZ>grq&|P34 zQO2h<*9{M8{gmUC0Y26@UF)@=^2QGM9)H*Z$x5N!9w-EZY^mj{9z6j`3)vA*YId8u z7pE7mRD6FBZcxc=EC2EDFHGKGm(a7q8e?2NOcl?KNxgl$>41q~a{U-1gUMMfMO` zhgKxdKRg&uQH+nA6{DtgeL*^xR0i4>dR z;gqr%*tag_o{DdM=$)>0^JqZbV>W&@t5Krs3Y#iXteR{d8h!-uD>(QGcaWHcc6vWJ z{%e0&R|wBrG09-$h>Svz-%5g~AUxG9y?U7-4Q%KnTyv<5K=#?*Mwg2}p+nYUpCagH1~Pyt z04u4bajJ{orAW2amk7NtS544Z?g2=A6PVQS!Y{xVBFFaxNnfY`tv`fQob~Xgi9LTF zZ;G!KiM}O>V(1rpl`g~^V8ebNx{eFmT?o(LEPL|tiU;+V<2c+FZKFcwMtDe33$BI0 z;T%A=|<3U?6kqH53zsHTXB@Q&Y#EHzy%GAqs4i9CLe{z+X~{)tndrT zxHdTsapOCXX%J{;j{0$R9g}C596sj|6(Y{P22Y}Dq!5LOcygaMx4|(iNspaV)@|IK zjzzrql;z$ROqr*$y>M9Jzy#K4+HyH!fA&MT6&;-I8D#5=P~#s$2!dl6)60K2t2}$b zE4IxIk+n55gQb*XLcB>>e776ok4;35WC~YotEXaLEHP>j1|>>kvGdVMuA2cdVMAk1 zKhVn;DUH0ec{%iRTi-1b4n1c`DVD6K#NMiau>FeLC_&YbV5xEnD?36$uWu75HGtOs z25D=bqN*7WD%9(xGFvh9RZf4-|E6nUGYv;!nC{Fb&&^nRzNBB;uX_1{Y0*joo^HhC zhWjt&UcoU4+#7saSRmiKjTU6eBTYU6)s{rW0iWaqv;+A3@%>S*W`f`4&uyqAxkxuy zRm}>v$xgAW_4N=2SM`Mfe-BJdYV;X@ICm+6p4j8Yu|U6sb8ejuDfEB21Bz-G`1fbS zj;hR;(ZRPI_EVvIwF3CNz&>_Fy={I4_YdfyHY&=d!X$VdsAaZ@{M@p*yjr` zmd?X#Vc6biy$8njNSf*1L|+ILt8LPCO5`9{Z@MH6phhdES+#$OK*X>%MQxG|^E*2= zNh}@MsmkwJlYCTc)!+5*7;vu}9(f2*ZBU%ME#pt4l$(xXYrF1Gjd2*;c^BDuTb~&* zxe~#;y0)vT>elhlaTpfcnYj(gPCjKUD$Gm8(Rtvp^-E@nu$GjlGrD}H_AS@E$+a{@ zUqs9h-TfP#X?K6rRooSl1sUXzcN1FPmI|Sn2&U7v?d}w8{F~K21poGyvq%|(xwoYk zo8l-~8?fI{n=mR1Z`nCe;9JTR=!}0mqy*hkah);uA5|n3 z>`QpZLiaCk#xB`~5AGKI{H9TU$(z$B-U^{>By^uFy1}ZVn6e-|y%T*mz*q-x+u(6vgp1P+=@1IlD+GUXG zG>#dN+);?EG}5A)M+3zGRPBKXbgVxD(|Ab_3n3qSB`;d|b?viMiN)$Q+dxsMX9&Sw z&L!KIR-J#EeQ{sb)ArAo&91Jqmb#_eGih?*$hQs8#WAAgUsTg6ybeoGcd3TJ*7z_C zY1i#>`7F~*Ivz98!#M{lUSSo_0~riMl4Ta5q8NB?2Yo5>n>W6sEnS|e5*}#-_Ff4< z7UI7Z_MOeqV?mpqWOkd{X{(O?&xCr1?miN$tMh-2KUI2G(ih_Q&t}HoeF;N4M zt9kJ2?fXP!7y4;lQRz0(5JN+4;Hd_7Uk9er&yyg!3-A!jWbK-zb1GWYAYptw&0a2`wwf)4 zLr{NY(a=xszrIOPL$GK=y{ahlS}3iL&ZqU8%)hckeU3b&fwH_C6Ony?V_FFYDI{(x z;^NWsFEl+j*-1kil8M8{l*tdahPbL+jQGsRm&_Zuc&DhEIGNJ!mMo}+0Gzly0|p6$~$>FyM%=IdF7sOiht1A8jEX0w{bz)7%3HaZ)F9rpsR?6 zI#U<6k+8t*$!A2#!3d9~|8Thq!tolPP5M=#f}yuKb%VEIBqV|~Vf5YSJt)fkKA?ZC zW}J(W(m&E@c+~Tx*WJU3_RN{_Ndg;z#d-yTiD2E5^tbHEzb8vE2e0g-0>`wpjbMJG zE)(G~t_qJ@nxaTF7RBlF!e2Z-+=LIJ^`QO794VjN(F~Hq2rW(_u+_fu6J%2FCm7Ln zP}|0}8ZkHwIxxv^>LpW*_vo*4PY{2$XuC#1Atkw9O86CB<thiUS^IJGJ|vj-#=TkI5v|zm;4?mz`vm z7M%ooKwk^%zRdx7h{*Ki1j9>_;FJ7?ptgOe_h?9i<*}Q$4?V-{zV2lPV4A;2{8Xqx7IuE7A#{s)JQL>gYqoiN1K#`E540Uy(8Pas-DMs}_%qxc z`bE;wHbNm(PLj-*V`j>P5TnZ92Fh{WdtTKEQ&~#ot&v4P0Mos3&A|Az5Ii6l&=y#S z=*`j8B_NjMbXGOeovsH0%CZ6faGq<>2PWSum#V!ZVG0!<+mk{>`}?R$)#bCf$%_F? zn7j~;?QU|Z4#w%WE?GcL{}bFud32z zHwFvI7wVx4`F&p9R22D$Q#V6{44OCR#kOGS zy<8eD`qpV_C7GPk{UGJJAUovV3|GQ@f^9C1**B&bm@_1f>GBL@=5O_{yksqyZx_XC zBadYR%iVwe%axVi=kBc#>M(MAw0^c%GYD|?#J9-OBonMdl;q895qo^E)sE5R2;>6X zVa9729cq>#SS9SwE(=VpiU}AzQ1*G+c<4i$|A$}EaSD)ZpGU83UIr)oZmw{3>|Y6E~vePE$w zoKXvR34t~bIN=hsNYPT>~DmUi_&LfB~$lZQ^$D)jWi${CLlw(F^m0{ zsncvHC=OW!g1K$P94nt&TnlJ;EEkKbZh0bEc$G8%xcOCZ}(xl{U$*>E`i*~zf@O@>17iZ|uln z#APgkSfHkxx-Y?XRCb*thm5MBMw!!;IS=pM79N^hEF&Rh7Ue%{L{S22*6O32b!x)KBx!w}EL-LP6#@}q z*|RZ$Ng>4?$>=r!qKgL*K>fhb!2Z3~H)pEM#f|s|3Lfnw=?3UupuQ!WwNiVrEWW{iJuSV5nt3Pu*%lK8uxTUU62-u(v^bX@HHJ~RVF zV)Wh-d%o#yCp+8CpvxI?_(Zgl9|KASdC+^D{?5(Tt~|Q}d{Lm>dL#<_oO%ooN9{fe zE)l~h0l_(LhF?~?%Zj79UN>3Zn_v;t+zPIMZQ-KZDY%IUR7s#zL+0N8dgFhNKPjYM zWqwTL>XWi0`|-S_eE5we1(Sxo08yz_?e0#XjNt;hH+^)-xv9x6a;g^9lMOjeX%_Uz zlGF5rB{D2DHtd8`tGo%72&GCp);Sk3xLUXloK$w|7bx{`D@ha$m2sk z#cN;*%e(jN^#-5&U~;%qa-G6b&O`vl#!3}_1}H@ZW@lk#)ja*!@Mh%s6#e#9mc@z& z+A|VRk#Nz69^QKjX$R{2h@3J%RZgu;{dX5XW<)`@M)Va}?21m5FYcLyJw@BF+53P&Mh=_9HooLujU^v}o3Byl#r`#Vk7SOJ?-zCt}q z6%Sk4lKe-#^5N0j90IuySP@<|S!qKnDE{Q!i!4pNHMmG4C~AN4s~^IBrYN8sAY`6F zcI67768GU1*!Q~Yg>}45LMF++Dm0o_-9C?Y-+{>7%}ujKT#-`u9(-%=ik(gLgJO;Q z8RM$w(OZ06XC5+}QQy{tqyCfNjcmK^gp#wUHnM^z9a>UNAe4Bp&dLhz2zV+>Z%L}Y zw636JzB)oXu;hQNS^s;4pR3&#Z2d{XDNX@u_-^zphOow-5~@_)l%sMXgXYwK5lpfE zH1J&z)a;#5-Q~TN9sKrD6)8z!tB%Nj%lvRAwrj@~w30YGf(>n%`zz(z6-u4$X&$)8}Z z9fj@G+#~e5;#D(i6Ey{8gu298gl_?(s=O5C%!O$1f;cFO`Gy&@Kf1p9Q#!&JD1oeBH0dtJS;)a4 zcDE6eJv0!Z6rRh?XGObS1C6agL`TT5@UjDc1Q=5#N2IFYeB*P79#m)sd=l^~6gzR? zx-)Vhm*|O!GW#}>e0K{?G%wX}gDv?Rk|3(+1|xs|vPi1#bF?FY*W_Kr*{}VbV5Lw) zU2+$R%!~68Pz?hq%kVwZhS#14dVYckn+!sGu4zu5h!6P~n>5YgxdT-CnQ%u586>%H z^?Rnl&XgN5m-$7~> zt<5lh(C|}W8?l5km@JXmoCq=zA;3trY0>zU2FKO^Gsx(t>R?HGBmow#WgfbK?Z`#=XUuyHU|Q4 z&^;11Vq;T}3$lGXg}iPrNv6`sL{%P<+b>Y}oB$yrqfdpT6ymdGu__EvS2!Tss(_wEX%s{AlG z%loe*kyY9x5%pc!mrJ}%T+D>TvME6|?#K}OF8Ntj6StZtq={cq)K_@LvHuXA>MltZ z>JD(F4P($&n*CXRU!b^n_8EnvX)=Fu+{u?MGRI@+-PX(*_CU52IZx~ZcuDm;z#){g zMns1OKh+*#D4h9jUl%sT&JygAH4Si2hr}oU6vQ@m_My9gM(hzg@&?bC ztmbv#=;J|dM*&so>Kjw zw-`Rw-vpc3sVBq%;sjbUEf%X%_b7dLuC1t`uTqUm-_U*?jVp)lQS3La1_QCHPte&5 zBf*n5NhQVSUWt6pST-64TL^#pu=jTF>u>|jmAL0}`g2gfoGO>n}iu)_;?ryh;_AowD#p$%7lm};f$$H7vpWPjciWfQS3d)kb)xSUmx z{|j2vSF8O#F;@#xs2ZY<%m@U!v3BMbIicdQ4DmAwYz8r=;Qfh}GZmJZS6ir~Qe*Bq z?fQHJd^d_lT37o6xWj*7{=wW3ZltdJJkQYdM&H3H!v)oXwfaFPB8Hs8)1JPq)`}kB zvjD8G2pQU^5eD2?BLD}_3=izp4Yu}{9AiD8ag7+_@1V?qnt!N6!D=RR2U;ntd|pb(d$;3eUBW4_8!@k~%j`KyMoZYS9XzB)^?` zO8uJ8^nIUI9u|MEA=Cz`?t0=fA?Se^k))&xi_T3DA_~e-J1eF}(QIH8V(BU}A`RbL9J{53MSOKFdsCgUFlqLSeH+W1jpFT- zkO+WR9Y81j{0phbuHwOqni)pw^_T?fh(g)5O+?y< z*DAdGP1}F|1>=LHbb}O5UnO2|C}@jPEj#rpr|u+JYIJxj@7uohRIB95`VLk1A6F1r z5_Jo8gtFc|2$?5O=cmVZj6pAKfaW)s1kE+{n0>W6A6;&q@*tqXlugx>D%d+}4}%H! z`4^^;V7BYzvht4RATK7{Y=dqR)wgfsl`6HD#*Ke)d~pigEBXs2{HO(Rn+|x)?%jl; zE`!P`++y@=%W_|6?n!(kx%7x{k`cqlGAG&H_KpTKA78p1z_Saz0$j;FRBuKwRF(j~ z=EKvRBRs)CTYqt%SE;DY3&jp~tVF6TyosQe+BvP@^YS%zQg*x`7*$Ek#-KYtPr<;FA036xx%`ugiZ0rk^ZDp6qiIJt~ zNZBIqJ1GRaaey*yQn01pDV@@LU$=n_WKs@MpvL0J3QeP5>NZp9&m|Y1JAdpYBABKH zNfwpOEp+TY&6kFPK@gOq%Qxo{s_fU=awmV>Bo-Hg1B~7MiLlA=?raXhX|e3Coy|tA&Z7Gk&=w_phFf z{ez3}u08=5frZt%K{f}KvuMMFJ+K#OxbSI^01FNd)3(?aM!c{*7x@`5m=!vkp*nv) z26)6)6=`U~56+O@-fQpQE<*|Y@w>BXVL9Lv~jucc$mkcA-XqJP-UBXf}{y6!*iuXec(Z)x{mA>`WjFpArEh!jD9xC>gD##1g zXd!{B^rGgD?=2pOMEUp!M*M#X01L%xL&+l)#$}DCZ4CwvR?9Kkho-Q8gjSl0b*-l1 z9{K2DrcnSkAS$M?of|JdH%OAs7WV|)ocG)QXc13O0AKb9Hg^j;Zt%ewhW};X1*Xgv zpo-VCl3)wrTFz=*x0tEXMxJL2eqB+yacuxdc1r3|_sLP91pxkuNQ8gAo)W=6EfS=J9A;t& zs7{~#m!To=l=+HM-Cjk+R7AkB_up_H1NYnB$3qWJ|vRKDIl__7-3zl&+2%yyqJMF#A-Z(q_QU zVUM#r1Dn>Y!5fktaYt|ER}Qi>UtM{h>(K{dUwf^~OSNBu?e&@|A`cD}Sgv)tDn#>@ZSMGwgSxmSNLSLzFblh=y{&EAt zRkSPqVK%NQfBFJoz)H+TrTzL3rskP0a5!?RN1SSt4k3Q!PD8!%)j6Cxw%?+JC}E^Q z-z1?LJ{ms)L6d)WvGrAF`*PV%FoDy-54-Rit5Kk_-A{kWFP0uRs;++0Q}wxqWV8-+Y{VuenKO;YF@q*+1iv| zUJB9iP?>-HDWBL+x4_qo&7(RE?yo`{%c|Xdi)W=|8!P^4Ewv#b96c=iEzkqRf$ZDw z{s6Y(qWpc$z3gty6bcg@G8uucG(6_wGR^5snc|SEG(7?YGMp^8TEk4QH)z$@Eiz7n z1hrNr0;2Vk8ns?P$FEOHwsBBlp^*%1#|`g>%$D|0#?L1o@i7`DyZCFb;)%!2|BzLX6zid>U@ zWWayBjodj`G7oD6>PE0UD4?cG3^-Qjp@-FkNn`30tWfq!G|~GL&fg?8OhnRdD88|a zCf0x09z*aolaY(JOr@x>f%O@l+!(fGBo|(X_St6^V>M5e&SjtRM~aHeFAJQWl2qT! z3DnQid$Ldv;Frdl<2h7dPqe#4bXWwiCx3r-gjuu`n7amgHf&vkl~hkD7szy1j8ZD4 zfOYenHte7ph|J`90#h>t#Kc+*v)v-=%7T7Yz2gtd`*R*j5?1Urj)nmi-?Zl{p+Okv zW)e9?Z$?b3c8t3d&S^hE|8kZz;7F_F0RF*D%LNLr?6USaKse!vu~>6Nw4z}rKB<3B z6?_H1UL{=;uDkBdYp$%>J&6l)$DIuU!(C*}-*esRf1^8kJ`NgaRk`AD(ABi_#*nmG;!H9!xW7T;r@7kx=SA=v;C195o|Sssvat;oPmgq zO7_g7aa7b*5aJqoqz^A#W}F!o_IJ~zD0Twl^~S%)B}iQN1y&%C8TD$3cBy~Efo4*C zU2Uy<8rJb- z3qBI(AA zSG;VAYZ)GOPs-Jfmejl|q0e2;cBJ!stIpCw+9FV66#E`4aL0rvh%v_paKt+vDye7T z-@OwI&9W|wXD-a;%`=BfaH}A)2a>eLYsVfy6At;IhrwQ41ebb5jc7I zKOi=Ko%(-D#b|4mkhc~Z7USUzrE`vOmT^8gxF!CyazWRB`P3bEokwP{nWxFWN+PoJ zw%-3%T~Q21>v=L?fyP__K;|-$g(Sl^VgX+?R^v|i4)J(WZ|eI+Pkh~^#tF3HC$0x; zB(NYtlFHlX%hZ37!q zLY|%V1gesRbR~lnt4ydy4o@U?r*WvB@iHS#X8F?jLWy)W2!2D869pls{NmfGd#lms zw95J;A^=I!G1+U6A|L4!6`sIzH)>qItZURustMfJQ1E}7XO!nYa(llR?yWKFYSZSM zY_GZ*;mL|-Cn^_aA;EO}NW`K3Hh>@PR$pI>G|hn}ULAUJHBy&YZ!=DM^$veU1GJ9p zoP{bz%Q-b?Op-VNw=uhPV`)wRN#@e^%`CxfqhD;C^;{q z_YjRjpN}eg#$XlHhZcd~_seEZov}RU0adP5Dwb7Xc)Xhq5W4X>28hTwcY*={J3z$0 zs9hIcRWs-n;64$H#$mroM_LArbb%-i69^|vcL4vD-4CmOl0fm`4#M7Seo>-)6$$4Z z?r0Z2`%^_(s3=Tk>HHRn^L2Iu@XE2#uv4)oO3?Ys*`Jn%^E<2+Ilty;(s;5d zm^4$|U~Kt+nReRw^sxel4h&R855YX?!yJQ6`JqV0b&kC%((^peH`$ zv@eU}O=#liUvV^sZgY>$s!WItQXiqB?3`~stj?MX`~L48&KP6DYlD&&G>mQLLi1;j z>3AE*Q0%i*5zfBKZ%wHcR8p4DcEnrixQ1I(-pR9nq+OGON!l%J*`9A%)N5N!%0ulS zQ~EG)#3x~_5~Icl#>b`5djceExFy-z**dY#)@ETxj-{?RwhVsFVaKo}!#$#P*F(R$ zmgu&pxEX>J2s}3RX70*(GM$$sFC*uwVL??{6khoFKmhy|6#3(kP^G5Fh-RYGR)>ct z@j;`1sK+_aH1Z}ctE&U)CySS);dpPxK(z-ShKWUi z4wz+^oba%BRvf9?sd6hCjQmsJR!Fai5{GwrfL|rOoH2Wl)tMe}>E#LF@2}ygnhS&9 z^2`+LR*fTgJF%|}=Jf20L82I!g%g4_Xg4~4g~b2Wavws^ZZlg)<}jkc50B%z?a=8< zP_PmWN!6@A7$xCzoERQOfc5ovYMqJyX7EGR(0WxYTJxK$Rj4xWFmr8 zj3v>ys{*CuIuJ5A`oJPE9Yw^jX#Zg7HKq@d!NpAsqQgCj!0e7CzO7OzdOYX|W#~@W zX0Hln81H!fk*0?0wM;rj`?{Ec>9mhH7KahbUjv(pND+K3F< zQcLIXAe~Et`k-|J_1|$*Sg!> z-(gsE$zheK+DHlo6;mPsdm|MF2ZxI=DeaDkgsvtUSZOiK>b&|WxegiHyFRjTTWOY7 zCH#JHUh=z{Te2XgPTY!#DsH}|qKR10@rkdS<4-NmA znRmK(;<5Mu*KOr3Q<8gW;E*X*E7AHCq{%oDt10ZiNXLu zQX#x@N5rr3?VpD1@e?ShKims{Q)nwfP>KX5FT{xI_BiXcED3fyNN_B7LOBgy8A8cy zVkvd15kt?iK0WJEWMH>di9>SPQN~m1;PH@M$txKRQt?rT`_ZNr5Im&Pu(|p+eVBRs z@ZoCHVgr69pB`JBFyMJ7!X;bdR`mPDD40R=-{f}CG_sKo1&Uh}-d&A9R`T41&G5DWf z4&%PvzLgrYK9VALN%X{jqMQ4{V~WPjd7yM&baP#Y2zzWH7<`-s26-p>yaQV=Am{!5 z-l1U5t@Q$A>g^<-f?xZAEk0~X@vC~w@)R$-J&+nDsCO}5i5OvjI&!OgdrliWw#FUXi&pEfJZ$Fgvg)Z@6-p@5$kOZ*w zk(+N_Ny61Dpqnm4{8vj_`($pOx-8 z)TK(_4JDl>NU2YM_B=J4-;#A~-`DQy!I=vtLWpUmfq`rbJP!KH^`|XvG!s5qe&z-o zJ)_@o)p5ihBg=p-9=wFsKrsN!W(DSNxtX1Mx1407^zNR^y%T7H2hF88f{X>dKNCz# z7xT#l_i_f$`KbLIcFXVL9J)e+awsR<3e^_VrY;)Q3#*oYWg}Y@v&CArN>i{He}TU3 zn|#!-+ap!>{W#%0jlk*N0@yH+5C7pubj)Dd%O#V!Mn1Sg#@chhZhpjQ!qiI^q5~=| z4Sfz*Ison^{}Y%;F9#(hmVeVyJRAEeWe%3Xu_(R6}^!C8KKca z|KpDtL*2xaS(ZKC6R$WhN&6}!C*LM6EuwW}5kozHk%oo%&oAzv=Z%5?RBO|$6SA~* z99^N{rmPDKER`i9pwUCWxsIm4cz>s zCBK=Dmk?&V+%uQG;#9Amp~Gxx>c5k;v%-=Im7-G+z*{HlKTaMg06mvQL=SjG>%9n)l7JM7$-#jOH8M0Ue*2OAu}Yc~R2! zs=np3tjvuG>Yg9+bq!owr(%Xiri~ExW0;1}!yIFF>Nd5+Gap)Kb>u7UiqjJ1D%Si>fmPOgRfy1MbIw1s)qW1&mt**W@BLtx;!!A#;q^<=ao`8M>UMO0gU4Q*#ocA1MwbEa3?hcK>VVUUb2^wk06v^i8Q< zR~jU5Zh&Zt6Tqw>IEbfMz5~zkk=hntF5AVz&j}1JIj)GLoN*ks-L>oQ8!xVK6g>2Q zHHMkGer{F(U}B##Hra7qqA81%&ZMC{e6GW=xG!|Ll^}f#9Pd5g&=Rcs2pP5}G%1zQ z{7Ye57~4cOhwvbV&R^=ul#6p^0&Io1je0H|uNfSqK(ZzOD7m4LGF(VaK#pue=i05$srU#uD z&x!zJmIzk!NsK7O^Tf^Tikth4J8v=!F6qSEFOyyrjcMFB<8Qy!qTS(=vKK*{`O09{_5RY)Jq?{(Ez)OZ>`~P8< zIiV4W$V;ijEf;&6)+nSo+YVEZfTMNSvwu{&< zybhqFF{|gO1NQ2CK0};=52cgRXgkfU06&|}fX3H{)8Y2O74h9c%o7~b794sesL+7A zb=WO%hc+O|ql{O#B>m2RFZ!tn^W&*z`I6lTOFm_FH_o^O`;hbvWCitV(06)zk!RyP zi^k|ZyV9bd6SCD+EXgU7D*p1vqtC*czsu6;(h9Ei{iwVAw^-hJxJbuN#K&*8XmnI4Z_~HQ#q-yBKt6P;M81y*VTpg+^oUivq$j2 zCG&uZM>wc1yNV%$*?FLy)CCFA`5`e_tc1h;VMc4m)cy{h3C_e5Wu!(`YMPRWifc2D zb`l5*cVME^2MBtB*ea4?&VQ`T3imtY1zmNhw*YgfdqkYIYiZszoi+36%;pBAd4ZiP z5l>HL3oh;q6}G8=7ooFy<*C3SA{i5@t%(hU?7|r#Ypj&hw7I88CJ_`YM1CL^`&TTc zu;+gBo~SkgH5RE{EQd69niEg{5HICqwH3hD(0zCa58SM2t3 zb9W8#q=}~~H@VT(Zj!B=0RyK`!Mn(OM9th>HqDod;B*ZmIi6P z5|VP+4+5WBRQNm-vR}Rnk$MTTr(xj<{QfrIJ(`iPeOIF&$>=`JKcghTC*9=vm5a5F zx-+tl8-)_ixk`W&BZ7p#L!Ec137y>mODeuBU42-8_Rb2*zbUS<_glx@%qB{pGwH@V z1PqZeji*rDirq=C(xObFBHGVAuJAiH$*6gmx-9{8y!Q(JW?Vi4wmmYN5mFX+Z1l}u zY78lXFR45b+rcqYGlXNZ#Jm^ln^SZY>)86X^_Ub5t9~TF(fF&)f<%;I8$Uf153L1f z`N@TUe2&Mv8559R-=>TRQxYJt?b4EO6fZ##TfB94Fr*iI0sMsAgE@bo95f!c-U-Dn z+`mQ8kde$uYY-%2EgWU#s8iER8=PLnrhR~8_;-7$Qm;=XNLy%yXWFFkuI1K$r_G&aumyS3W0Frt8q%TZw?g2fb z9|C$t{pEo78Brzqk6J_KMz`3Ako$cWu=IBn@cR1SjD3mY^+uH%u5BT^vSe#r6YA;u!4v*~SqwXc+a@`klo5hyDNJ6C+*n=g6}kqJ>Kuo zUlJLCVzuGlM8tqNu>U!YI|RO|j4@rOAgow5_8Aq>A0KwF392=nrF3QKd-f`X3zFP=4HHRU=7_sc9`*5Pc z4M!C$23)bS!tfw;Xg!<|->eaT7CmKmRF25wYc|PQOAcg~-AkSnXq<6Py@Ch?J1~wpTTPf$_Vy?p* z6Z2LX0^kQ~{8w%}2qJP9n2TKG~6`q`ukfw@C9% zgCF}?pyl#hSB%r#G{5#xa#70Y`Pv_*i_Q1GR2zrj`b+GJo#?=)-69}k&SMxf9jHNK52;91sL6jL% z&4q!1fkg*>?-+4^C_!D0_u;ZnsMj&~sVXJT^@151SJ^Yy-XxC_;m><8sEuW~<6HK) z$aD*F=Q4S-?Jy<*@bIl6IQ3u;6pMRGuJem^5@0dGHarZcoc3O_))xU zqe!vRGVFMDBdVT(6Us0&hk`}i@*j^}UX_FLMz+xTuQQW>3^XMOU{kki$Pf@)`yBk# zfNW`<2}xtA<*@&whwPnyCdpu{sJ}fVSmhU9%Z^@bDy2AiQ8y<3M1G{6BQEg?S?Gn~ zncCJ+G=R0^aHALvhiyl}`8BT zvquc;{fEpIJ}kbQ1oYK`ADWHY0TDL?4{dn$feA~P@|XBph-R4arABZSH9gUpnd{GF zmglR}7LH~dV^A&+b2SR$cftJ6{cf@{qed;VrycWuofa>$duT4=GE^T*!13MGQB#&} zW)1-qLYAVZ=Jcp>xhm?&+MIgkSYd*}UNY5q@AIZzLOFWSN`lrd7+ol2;pl%0)2hrq z&dU{1B!E;>oRfMz@jSmqS658=^D;aM-DXF}!KBy|1Tb z!qjGetAeBprF6Y{hqn`t_wR}{Hb9JO0rY}TN`vF?`EAu_5>ze5aUTyl;1bMD@f|4TCHr~;ufRjHAlknPDIiqt-2Nr9;y+tMdxX1aAbuBt1K#Ep zG)488_Y*5RZmw5OWxG5wNNxCwB9{HXU`#gXdl8o%!=T`T5np&hdS&Bd0Ci!CB^jcB$0WqncPxuE$(4o@!5rk_$InTS$S0L*)K>(* z#Juf+4IHE)w!tax88YEv|2h_&I-Qp%wp3o9bS+%m0 zRMzif@#ab_xkoBtQU(W#VsZ@vf!7F}2~139OUDI9Ryym_)5_PtS`1}}&9_m1ddOX} zDkCr^UMZX1tJ@hgUd{h*>g3uM>TG_U^%U>sl#J8~?R(!9vJ9&~{98sZh3HMPareRo z_6M_lNwl|o5p+7wxFc~I-H#?7lJh)}t-?)5R3}+WAnei)m=P3Fw`p-0@#5X-?GP8T z@|1Ue$@5obo}u4J^11ISL9=*&Usr25YIE@594b7sr|U!Jeq%}RV}6S9EbJ;IT^U?xD&+uzuHJDekzR= zY0ghE9vRP6)?US^2>&j4l|rRpFUq*^d5i(tIG?9Pw3|v&Fv%z0E?=B~K2Fp}Azpww znM~Q-!U;x;VsnM{yOIhe??{Tm(1HPmt%C`8BH>hrZMmXap$iIAr4dLD4%-k{`fbnf z9bmc_=@M=3U1bRtA|fCFong68VGBvIe&pB=U*GL3GbB5Qz9Tkhs@=jzT;<5&Gtdh zxRTx0FA!SQXZGQlLm!iB%wEjYe*GN?<|`*E9SgCv*CLm|*v?HWHHOY`$U6c_{kNh; zD`B04ncm^`ybu?|13>q+g)wIU)Rv)*Z0fNWt|SCdt%p@y?q_869G`b56l6)l9$>*n z_fNCpMd$eHSltGHz}WFfV@~`@<%^j_yuS&qLPO}TcSEXu4pd8q4-30F{$-dYY+n(+ z5N9>$6c}dMKiEF!l6Q=YY)HfMsbp!9BB-Ri=rr;d45l9zANWy)^37<^{*>wsi(wGJ zuz@s|1BHW)(L^S_a;!k~TesK{NG;cP2^MbB{#a}q&`p_tbjJOs)nUp^dMJ${rAM1f zdpl-|?8M@3)OCpQDc z5gP{2Iu2!j7LL5vQ1*NMo*|AvKb&2iv<__}6wxO6ZF2qwTBJ~4-BAibZb4ky1e_iT z!VG%9d{PCtabmq;fxxZ$Z>&C?k{jiA{eF z$h7X;ndxDU^Y};k$#a1wYPG>j!B%x#iQFjLPT|936&^c`yXI%NAU_HJ-m|28WUHc}$IRQ7_jh+YcFb<3 z!VJxS=Fihs2iNeI$h39;UY(7p?h5&hs`q1|xz4+EHZS;J)Nx=BBt@`3W?8jfUd%%( z83n%-fC#1O3~!8Oqnr0!O8Tg$s!G0 z3k?bf*&2Ba*n-vUvAwYFFk3RL6(JZpgV>JVv9+b z1`8-GO=6>W{){a zUJhZ=;G^;nX+^Wj!6i5L^`t<&w1fiLI5e zv8Td&!^47B5A!Dk4uQy9>7PP>$DPr!^Z_Oq6Vhv~KVUlhJljZ2< z=8&12UAJ!bK>zMxZ0O}nR8PAh)B{W+i8*7_uj129Kwxc$f=U+bXl zfUxa!R&wuiDpOhKYsYWvaihk6pAJueKtjavrE6oeeMLcyW6-#tWt3PPXDowTEaW^i+I|=va|^PEa!#0;RI=RL6G81JDY$s``i&jOAV{lfR><0;_`PODy39NIwg4U~4Ug zY`r>Wi<`wP>MiJODA5{!h6rNXRnEoJTJ-`IIv8W>CQ$l0+x;{n+&tks{nZQWoi`R} zG@Y>)Bn2)O*O!`qhYh@ME$SQ`vA z^>T%BWv=Zk7Oapb@LHqW@@QvlJep$k;#vAp@Qp3#kqEA`3~>|QEm_c8^ME;o)v+6u z!^VRX8Zj+t67MVa-R$zolRf_IbP{-|I2k`@_*b`nB}Uz@Ucj@14xJv&k@)<>9wA+w zBtPoiAU*{rW?zwiTa_EW-@34iam@(DE>YBO|E0Ko`{OmD29zP7i_m@Mt+ytHBGRZj z$%7n!)^7pX{@aSH4u57{E(pp9Do}I=Z@-F+Ov5*H)Vg{s5pH@Ba;4=wAVso8;aBSv zl$N)<;ABlx8bVn0VK#kb$xX|=pZ;mLIvOS^NOLEoZ5b_pU+dyoAe7*rpvH$hM2 z&k2@oxjUv}_5gg@9*Rujq1zypxgAzqxrjahH{*Trk_!hU7$(@g=qo>J%Gv<~q9}XFeak zWKdbLvmb^b0F|;mrWceac~js&Un4-=|CaZ&vsPAb;VrN<7u~wAN-RT~dYV-_m6V`G zx6(_5!-I;T0l;QR9+mbyTHJtHduA!1sR8DX`hS6cwXWGuS0rs3t*2*@sEx`VgNk;0 zX_d=MO!w(y7y!-Ac7jP<RLtF!$`mWHQx@T$G^kqD zO6(mV%UY7?{ubCpF2;eR3XtCX>1REY2CC(H1>7Z{ZVD`rD}y?-Y+QztBV zzncj-ZA3;tf1^c2oGD%89_H>Ow@wH{>JY*(D;Pc2uKT*EL$0C0)A?z^y=44mwk7n|Ii-u@dDSDLCM@+{Iw8aok1*@S7>ng>2B4U`fMeC9Y0q-w1@@23s;+`oXC;}U zHOP6>ObVZ=8&_+9y_pSPhYtJ{V6`)eN@sRPU-U4h@2HZ?RV#ycP-`~RFxoRMFrvCS8&E_Hf>__okBC5Z-udx!q;JOh|D2}!20uPX)+#5HGGf+E>3*z#(DB1a zHOAygX`yJAnu-nwIYuaedy?WlBG1IK1l#1gWZ|hcMp@cOiBIX4C<9wAda|Ze?UuzS5*OTT! zwG+oCTn(v^yEqk3PSUBLk~yV+t1-l1t-6(`!stC${3~NAPQe&)T4uSO_9@RRmFfEI z##PhH2Z+nEXz|%ETsm@Z0T9#(7sWZT-!rgL3Ypldw#SWTuso`Wv+~%Y6$innS>&=# z4qjG1%O@Dci>T5a-Dle*=CF9j>*l`1h*SkJ{VRNGp#8>iIvBzDAgjXJ= zBT`c6SD&vh2f##=bV06vHQU6esc_E4+_$pYq4W+80FPWz+Vt(B>7yKr;FKlAV4Sp) z-y){+AQX|fn7mn13Y)VGB@Nw&QXN4+*QxXuc_r81pOwsUARME>9^u~tk+=&uxM9>% z)VM%GR1NjHa{f!*4T&W|_oSaD(^e9ZqQLpE5n&%OKAXjw&Vb&3xSKDcG0?9fD*3&SU~{7fu8r5s&nDl=1OMTv>!^zGz17e= zZrh2vD8zsXodKIAfqzv)&8%Xf_xc-cvSA4?`o}6(c)Eyl_8eyI7?`@JUs@X5cRU}t z^JA8X`yvI<*ioc^C7iLf+A@X>o$j$cK4uH)q91%_8NTLP%$R8!82}o-k)vA<2UQs` zlEya$=U48^R=#6LL0w#9q;4_yLSlZ{;gNU8J)~q$ zqAb#*?7gu6Z8WKaK{^)ozM4mpI>}^BkG22E0)InLS(_SvYU1I+CvW1(8pI!B6~@bz z)SXuC<0681r1ZnHefHHIon#8Qe~Ik4M&Dab_rmw{UFNLOF-G67k4Wxt^bR~E46-%_ z5WTYSvH$q_Tu&t@m<0#M?dS{XGwjT2MFA>;v2OWbw{#`Bxegy+_pSj_r{do82_z12 zq2X#PP}T#;Wpd4(OA)g5FzWVr1(X*yb>5Wc2^R zF?OtU`a=4AeGuQJn=|`$XI8D)R^sRzxo;%DqAMtT7RfCuZ`Nvjw`1dcU5YPH zVeC8CtRQaRdfH(VvipadTQF33yReWOx?bL|RsZXM-H!n`7s3U!)QOoYEE7w2WLe0>KV{4{AtWvei(ccxhW64<3y)rtBT`(N)Xa! zjil*@^AYUOoSH+{^f}RIrI-yuxUvwr0%~I%WiKiAAq~?ny0E1L@Lt4F*pJ%e2g1a_ zd2(8CB;t39lg-rQ;#IJ0%w87o(26PH08Ft=F@7jhyr6d+^9rKNd$hLW58lgVi`j>N zm88W>OL2C7c+UPz-NGB%(5cJ+DJnqmH}?m5=Y2Vqw)h%n+;X#3(@2TTomtG!8(7!f~+*;ma}#eJ>XQObHiZ_k>kzn`$fI% zzcSd4RRx;^t&*4js0x$G3p3aM<8TfOh0T6{`hCk;yaiAKF>Ph8=MEQzknW=e`sSJrKOLT%q@2`o zjN9TBeH76exM+07IiYTJrv-g&+LTpj?M}@NrX=-^@+r9j?EzVR+M`M)Fkhtz42fct z;$PoOI7x`-z3Pl>@TVn8B23!)gJ`&!1Lv;8rudnT#0h89cUiy_&)TVfC7+NSdS0ue zPZKM3-(Qg$>_LzXoWGE3)NTgeifG6pV7TpCF3~WHh-^Jl7MD_ZrVj@YE&yv z9wU$J#YW9KkyE?@f(37Zaf{+2`>UyDrLI;NzI;u8t3_L!S+%B1Q4Vcj zWe!2|bUih*++Gw?04tJTmq`U2IRn~mnC5SvEO#LSGTMURHpCNU%nFmx1t?5XM`n7U zj*O9g#E-O&LtX?d0AOe>+GqBaaYmJNVh6y;?}FzF>Zb~GjvZl~1_3*E10A9or{Hzv zE*%{;UvygH5)j;flnT4y?Rl{z{C`%$VMFcxdad!svnT$0_AMtH+ng&j`!b7~hv36u zg21-Tb%Ew1>{*Xw%i%a%r|tv`tJvXpVCAzHqSoBcgT4k2(rgT6qyJW?>1w?}%31mj zJ?0UY*ToqT^Q>|cXBWC(4vD9Htj?28zO^27=NG(jd22vbF2+CFHlvYVd< z3no`Hd|#EtYe^t&=jIf3x-*!|7dZ-9c~(eNybEvfc4lv??sBh4h>EAngm=4mJyhy_ z&*aOekfUyY#VVUspp)+E3sL?k>gfAw-hUxR88&^T8hie^Ws;4GHk$DG+PouYlh$;f zl=rlXOXc;+vwh!Gh0DW5`E0j=H_8WG8>~fI{Y9ifbSF&BeezD)E;r8O*+V4n;aqoFj!&5L2#axA6UI0@&A$+7V#hXjLByaf&w9LXVDGj&5ssMQ0^=7mS%~ zcE^l=!kGZIQBxid*jQ!;T^HbokZOPFP44yOBt#JuVgnAC*go0Ts|nP`f3=U<1cVxF z3JIABS^Uuw&*gfj-nZ@*4Hy&IR|hfRErOi|x9#v0*go5B{SRXsfWg`lina+v`t%ow9&SsL338Ba@JC2eb}x`U;po!5*@! z>-bs(5HkqD@bpZz#pdl?mh3+MU^~M&>>_;KDg-HlHIO9nnQ>s>3b^3()ZIdGrXM zNPNonL&<5hM~=7(?|eZSXM1!fvQwPPJe{MfMJWS-pjc2~!$|`CydavJAaKvUAI`Hv z-zb~@BWLp4(ks_*I6Tr0+y$-P7nJ*drqv!$b1d;8s~?~Bi%YA}hU6V|sc8O7;ZqVO ztMil=x~t|+HVI9^`XgR2Sg|TZV1L3vOpr;LOQp!J_W4~;l#L70<14JtKJ0jyiPn>F z!8E`VFd-r=kG1i4?L5M5zJt%xr+E@*w8vuKSDQ@5>9-8|_n#jmuC$Gh7Z!_udB}C$ zYR^t6bwem2gOAbV8Wqw+nuI;Z+0Gj&NYztZI{DXzi*IGRBo9X!e;N6(i3_xAZApMS zyk^{S_;QU=8@>uDFfHrE+!Xt8F@U0?(sLPeK=$XWSEzxR7t5JEcyOR#moAIJp%djRadI)05qha&mbqb6v z-qfl;VD)a`S0N~G60p_Yr9SCqaGTkNS|CO+SOh>50FQ~c(Lhcc6C^Qe&CvaaAn&yh z7i%}<^2+v^UuF8Ddh1xs${98aePO88na91&5<7_|nCjS{>W5$Av~B5sJw1_U=s ziV4DbE4K6JwM0TL?v%L?X=_6%u1w^%BZ4{9^0TP4JCvtUu@ATuW`g7;JweS9HLwg0 z{<&CC1AF&Nbgl_;B&u&mEYUHe+{JKpNv}2d6leg3GRya)kwZOyXn-I1SB?j(>b?x| z>tpUsq*lS98}gd7N$S$i{fUk7X+5Bdkwc^#o#h3)-q+k2>HhR^D|P8%FAw~|biGEe zCv4}VEbz2um2T`tzU3KlAusVVI7I%{ZL3zCry@3vt*+px=%JqK!LW=PwTm)KPkUtr zL$KUR`WZ{SXFrF3=$Q0+cnjqMgTsZu$Fdz_h&W?meZ<=NMmrk^Akv(q#IeBJg2Xwk z!iO%eAkV<>vxdqo=Z@2nN7ZT-efjL*hHR>JpEA_6>|wib%O9#2Q4yQy@NrJ-I~>`( zA}~<}Oap{)#-u2|1nuLi;mZdp&~#EW8YC{UK9?Ss#>_2$;zwP{{oPk7FUWvN)xcmY zQx>(XAn(pWe!iua>1{&@^popdrko!;Ugr9g(Ap}{0l*}#P=3qCAsbJ!_~`%#>@!9i@wTN zoJctSrIQX6>$n0Tb=-d$cIlTI?fOC_8DmRg%$4e@abwyt>z?G8l9|wwPv+sCGGH_Q z>Z>f}npCjV+cYNZZU^diybXnaTcNov3T}#j!coA)&;{`m01aIYumeVKF4wCc-%E4A zgX$~tP!?sDg7Ls%I}7HZDo3*yI>=Ft1CGL=RskpCGqDj4!>Q_n@e+O_zKd{l7A<-B>x})Gk=JKCcL|-z>r|pkQU1*Ck7EQP*w9`1j3TvPdw0HUH*G*hHgwXW8zuD+|r7$1FL%fg+_9U|G6vEVHbj z)ECbX4Q*i$3n`~xkBA;rJ1evW_G@5K_UtVoB%_fwAC<}?i~7cU(_uMDL>I(Le_+uJ zHqS%r^%qKg(JSD3R&$u73wCK}p*2Dq17tXB2E zeh(2GaR^DRLs)ZX(a&y6-8uV>+o(Xo&To%fkkGnI9YvDkRJjPk??0}El9QQLt?5fL z9GBDl4*t-Fydo$KYyp1qttQbNYwwgyzHir-X072C$h0&MPbKWKyRaM9Q)oAuk`|fF2SUm}5kzYg@xfJl*Oatz69Cl|D*iTd47a zVUKysLYaeDG1bT3yQH|>c#{K@+61rEq7uQ8qM~htdiv0x(N%YUEI2I?NdxF$-O)Ou zX_roUhxP)cCU>!_tgi4Szr9u6x043)J9GDiu2IBPShx2_EDt{TTTyfkp#D&l$6LkV z!FXJWGvcKJPzuI;^qR^BH2a7Pqxbs*YjJ7?X zgF{0)*a`u^0tGVOh+5_Mt}(gT798X|Dgzh~w^gXo8TfJ)GXYUMPT z!--%V7oA;yT#!~Vb@mqF`gE5&6Ib!0Ia`49S$qw2_U_JXL;)H?Kuc9m`Nk>MCO;&y zh{RjiiESi&j+){;^?UJ}zGYgWwHqinB^laTb`7wtC{qtfB_F!(0ulSIjkizS04O`{ z`Y==Smr-rW7SU9?I!3unZh9E-{s+(thFXRbH5wm(ih_cxXJ*Dwn(!lms)rNI?J%nt z2>CnET;*1%xtDOV8MSmvugS}q%nZ9L!l!Dea*c1IK5w+LX^i2qnR0P4yn7vpW_HJO z?ZVrxDTwzQnSHkTqo4pNn#G{f~iKNVoBOv3x;uIwiJ#S94zKx#CmRs&$ zeRK_~X&d~oo6bBcz(s69i& zI9aYiFI~HMS*9QlQ7CabaEfYMJHZ$~-Ycb=+eH7_{(z0a!--oL*0LV(l_2&rxugSC z)1#!fzy@jVR!~_Rj~cfc@;9`f#`xWzgYz)Nls*i*Fl2gaauz*2FSY8o`VnMAw?#95 zt)gZftc-z5y3H_kg@yYCd#X2Nil2O!BIm3LvNRr2$6ak^r}^W{JAl8rK#vhVc4`xJ za;+@6ZO*FGU-b=F_SN6TKQ_f_a(VxoCOMsXbQ7D(t+yX@nO=rKGWRS3VupNMN1fWF z;_LbVKK5$okSxsuj2sygzyDgWnx>{pD<;_2SU!mxc#KUwcJ9d`>7mTJQj!$;Az*x5JdPCIT;1&v5QhLwFha| zJb>}q#e~sn9J&Qy^0SAha3itZ%>F02n~{IfX?0op{@qp@A;z{XE+V$#BOvO3q4Bl4 za&(4E9tSq=U%u2Cfs+>%$mB{6fL;$=7_NARV%1-O%g@d=Jw-(+yPi-q174xG5xpup-c?G@nx1Bt9xvAw*Gx*M5}+X#0G#AS5_ zF675nnWN(eS+FlkR{Q>?su@&yrg5iHJ)(Hk`e^?!*%L)dO#o%u2YSnYl_|aTpvY;h zPKW@fR0i^T@Y)C&?=$^jU3Xy#NPO-=mt7J!I7G9jmZ;*7QIl^atY>DZ79^#8POuwx zf+M>3sm`Bv;Krw`e5kPDtE--4a{1HZnNC@1baPjaxCXIwnw0$O%sY;H)so>$yN^>u zBLMpw-?+b_22a+}nnNFd+UWragpN-mW&6S5hYD5yAZ=!*MavcaK%w>uXa9l_R8 z>OcmQ4yqNgJ>Y~i_gs4OQPw2Tb%dR0N6n-}(zu!El|u)b#`cqcG^M;4pX$=o<8l6Y z5kW1{7)>$O5qAv$IY7q020B4gU>k_up%>|Sx3nGlCo)}jVa1%o@Z?>IGo|(ra}H4G zlcyYW;X6Nbn?g>A>>60S0!kgtqo0+gogAHtT#Bz}R{1!-qJSr=|8H7}TJG|qfNNb9(q>R_#u#h;B1xeH5aOOUP?^i5T*;Q)!Dtx#o|8cUepAB{I5+p6 zDRa)ZM=JC;aG#-ux=fb=l0~)Tkqj!|l_>jupy@*@%Uom3YO}}IaGmq5l5kqKdQ{n6 zGOjC-A|HvzKUFKE9d z%8AIu*CdBFetC~U_8Y~kNh?LO0ZIWP-)es*#w1v zr-gGJ2@%eADw>j?(oaW@P^h`u~342RF;I;fK}pzvb5 zu9CKYHDVBde+oPKv4{j9ssP7hmo&}OIq>3F`_C8ZJn?A(VNmFjg3Tf8mo>`(J-76@ z53zx2aB0#Oh$lh!BX*+qUa0yO8bwk}y-7?A+zzOJFu$)Bxe8OKb?yaQ1+pTMtVspr z)?pdVC?AY$_U3B13^IEqn&biT215vWPnC+!=T4%`UolAWSHL;CEW^oQRap*~!)c2Xr5GN*Sy^B+vG)MqZJJ>VMfX?fv?fpqMs7h7CTtaE-?`VI9BGM%%EyOhVOVT~(+xORh4Omp|^Rnn2-2mB!+X zyTFpfJ&Yl)lpCZ?>TPvTwu7`l*`GO9ghQ1Tf1R?DLBv|8JBWaU8kXE_XYuPhdn(?1 zyYhLoj{&BI`|5qlXPzBtY*qodyg}Zol_;jj6gxFKt6T8Zf5Dt=R!@DQ01cQYO34WF z4WqcF&|Ho4u#Rg&X;=wC9{sPCka(;1MF+qM6ngk@26h`fk2t#`CAlSspvY{IzZ4cV ze>T9Jgjr~zZAF!rxyK?;(-r3alnsvF3zAu)$*6)ThAZ`pQo8?8^P~--qB^ItHk!Ve zzfmSxpkNH#+1SG-b?MpL%Om$-?1KKLJXd&epsaQi04Eqr#2d*&w*$Ts2RZB(P|Ow* z6`+12A>f=8$?Jo5ZUkNiOOOJY6YJqHvBOwc=cL#I)bd^1pyg{1HuRaHMo6A!^ zuatG(?mi1DU~APuo_Hwfj><($D&Swx{10)RXi)(fk(+a|Kd}s=Xi|&NI#V&+`DI0Pb>_*tu`McR_OY`MOOibP+BBNTT5p%EB=%-1vL`Df7+&{Gm_IV zu06{DF;-fRcwem`Pzbn9N^yko7!Wxnm8PL^)!0i<`Qbc}@+%-nv*a69)m)sb<-{KA zPBm5EmWDA*h^VahNkqw&I#>~#23aUnXUhJy60iE>5#;yp*noQ z(h_dVd+m@Lvo2y1?GAYoe+W~dkDJv)CMKc;AB(EjkBG@|``sxO*b{M(0%UjoFQWAr3Q zO^b=qgFtlHqi>VZg{zL*5}ctZLgKT!O$;VAkA@%tGfKCS45zj8e`!GCd_u+C^1_XD z;LkcE@^(uw3NI6$D1vYdtFgoS6phKg5_$LrLGNUxLZC9@4nDk(zVw%{vNI2 z$Ge$o)9ZM695CroFKrqUyvYq+JZ}$N1QybKANS_n;4)qe?*)QQk*2{&<0$Wi>&PnL zf8GRhI5jNPr)JPDe;)up%?7$tZmUvdHg?q{k3o|Kab6Ex$!*qBQ1qLy49RgaZM$S{ z*zo6@af882f0_;S#gO8xg5Zsq-?m zhqS3Sff;XWR9~%mzASNqEVf#J!J!^M_f1ZcJ|fezf6DcPC)!xPVh$i*C=@^s$a!gc zEbor7AzJ3>Z-hi0YFMai?jW9(!Us-8<6p9~gTL1(LmQ{)k_1AN!DCcF-;a)6B>7qvk3h2mSY=VwjF6f|!i+3Z7z{t*_*leM ze>Z?fvQMLcrHvUAJl)}dQJs#>dFq_x3|ze}r?#o_6);P+4vas7&E^14+F;w`a*LfB zrq`<`Og)GV1H(1`w@``wh4_?ur?N%CNkQ}OBPQyTT)DG1)4U%;E3ANwhrP@EcoqZZD>9 z;=**pek;@cC8NqoeUws2sRR}tRIU|+8cTgVAzB_W`p3zi7mS_vaCm^AO>F~#vRNaC z@_^?GVX4*ysezMLVdCfd6@vEW1XMmv1bBHYJ?!%%c;J8+h^qI@mb$n?X30J)e=3E; zi>5M_rxr6Z9Oe;a2VOMBZNwY&fsm`bVm;$_K;?Y>mPwJm6@Ep}?X zRu_y1eCc7+OW#5-7JFjU+)cVD#{E7mP4O31p#D@WSCkE1gll!W^DlZ-{4{ zEVB2R3?TA>3;QeI1e%W}e+A=m6ppg2e_4#&0(Q^nRV(KGcv~rYrxovrIn8#l6DPT7 z>^u$XG8K`A+3C|wPO8&bxBLeg2&xG-X;nRa#($}MW28+vxo^PSOGlHYDjJmXOJvU&5|A-l&j3}gghYDJvfCLACMTAxHh6**4myE*f5lMGf_ml8u|i9+ z%IqN?BB9?XvVvl6L$qau@(xNzb?0A+KBRxzsuhiGt9VBi0zX$3@e8)s0-jPCV73v9Vs!?Qp;;T^s#4j^eiO%iw20i6b) z*c(oG%Zoi3vikNr4^MYCyP6Aj++$-y&v^DsY`gMEh+TbG*Js_qkU(w(C4RGQ8oq}7 zP;GBZnz9NA4D(#gKJZ%PIB8x$H3Oay>~8uQB#sz-^Nm&Jf4h(qqiw<-M*m&_yw%;9b@-Z~9#%1tcnrI%l)%J%3X8)F#jArlp zNf^Gr(G-D&%vL@HOeq#9Ba(;J=bY8-e?q%1X#m`$?Sf4H=Dh+v`4f0omR=W^?v6wvxt ztxD*o|4w#)$#cRY+&Z-cKJ4m7h&#Bcn{OSi_g~`~U zFnj1z3I0Ldcc!11Y$Z_7+X~CUbe_!KfMLc~jfl3gg!$wD0#9oYF~1#FbTk z2{M>?-$eJO*L0ktHF)*Oy+&6RT1ygXRL+T0lhqk|GqFHvP!xSd`~QfJ?I27PegokK z9Kl9qSMs;n(LA}jn=ANvH3YF6R;ho3*sq53e>m(-^i81KRPxoVkB;0_)@SSXIT5_H={I;7(joyb|NcFJjjq zf0NZlyI2=%);RZFbz)h6eu>!@THlRF0evQ$TnhL%>h+|8t0dGC{J7+j7*;)$fF|Jv z9X?Gy!ZTGiIqTOrfzQQZX|N=|s z{gZfEja=Z*MIbrK69sTxB2>&p5IcM&T`=je;7$Jc|D!{L8necf2-fS=;;!{yObZ@KaLBfJ!|j{ z-|+-2l}idE8duUaP~IBw<3#gbf5`H2sjj6)rfUm^SFv+bZN{}^~zv6&uX(VUiR~_(wKp;z*h$*^jvYXvX?#kINh~KO23}2UG z=F2r+gYa)bF*-l9kvFZ2f8spqARSq~g9;mC6GD1}Gn+J1G zpRq*a0yiXqzOE@M&-xz|QIZOmb)k5wu;_J74~{LlP3U@>>MGgWOIknlPkS2taUi%? zUgzPK;SuV2YkJDxzgVtPZRE$n`VwW!+wOS%@U(Z@O3$T$J}TAnesWJ_a z`){nzL?IU-GYN}Df48qCtR^&Ll5`3eAy6k}9tMS(3dADBW5jB_)S8mH*w#3pwWljt%B|4EPsBozkd2RDF(9?|?o` zah&eC;(>~DXl(7oTcH@J0XkrPt0$SdFs8ciae~y#3vN!PX%e*z?>h98b z**5R_v65^1CJHhjRK>0fYa}=aPX}roFDj9jbPx1mw_evC#HvhSliJnubKlL>P4sl+IhAdL)07Uk4Jv^@V*n=VfCg!g!b!a zO;P)PO#am>f38}U9Pa^{AT@urRRJ~Yyn^67G|q$*k0Kjj%$eBsM{C<=pGTIf0dKEh z=!t@^tX;&}c1?;sjl$e;Z4NTT`h_~0hQL4a8G|~*rniJpY&`0=_8Bm}hL5{s)i3+e zuQ=&qe?E(R=7tVskY_+0^_qBbPK;xKS&PkxL{&n9zy@zEozJ0c%8>Vnq1Q1 z@{OmYs&1VW^mIPY5kJ%dk64XdrZpcna|3MjIA-=Bvh+&FGq@s)<0Ge9T7SFvWm_l_ z2*r9X38@`}uaYh*kv&^@IIN#7PXJlXte)+^8|S#K|nk z8~^1gjlrZqZK~Kf2d=2^Fz)1;cTZiqJzplKv-9n0pP82V2a*UzR>xB!Fb?5RJpM5+ zctmbGtz8}|aBK2|UlbI5*i5Z*Az=a3q%DRC>2+TSmuGD(bDsI8OT?zu{O_mFCBX z0P{*q(x^*zvir;6QOA9zrVa&f)SR=DBu7)Ed3kjc&9qI0iFg5~@9fK38u{HEuU?Aj zi$ppkp7qDt>hE8{<$j$@$Y~Epqk!%7e|6bczc>t54c%n#uv^VeEIO}cqdQbB^(q`G zs@6yz97BwHK9%QpXNQhWdl;yWWD~dzm8{y&ynUJUm!UnIApQUe z@MPt|Jl#R#LS`bOaZj}4E$a9;2{k0Yq#}E*gzhaNoPPQ(M}n&u7MtWQVgj5|fBSaN zNNg*-Z4RXNmMi}<(}zPE7Aid+|lm+L2-S8>1U>!xQL*x zX^DJ9N5zQ#iAe%Zu0jA=T-&5E;~jBWQjO4AK^C~^$=YnP_YZX6J@w%5hu?^3gKOn8 zz>9v)WcUS660{dG^G7Os-QHoFf1$6+D=WV&5d2{ak6b&F%*?Nob{}h3iT22{)`{}H zCHuATKsdMSW#K*$!fncUI;%6)^F4SsZhg8o+5DAYd^eGQVez^YXLk`z!~SO4JtVN3 z0;78Lj?f-Z;!fX#GT!f!YkDP}UptDjrb7I4N_|5>zKLBlKxp-Hgi-Jme~pnoAlAG% z2^};>*SJfc!8yfilJj~v3JfCe&5{uvTv+(5+f|S2+ZgC@t`!(1+5@INd|IZh2l=NC zU(?;a(MNug-Z;d7?Pw%g*1sxu2EtdTRCggK3Hss;jfHza@;XZv;%Gp%>|)(TMZfxx zoNroaXIRAt8TN(edz`)9e?Uq@!>Ar_JfPTzNeTUk20$J2v@+llIDk+yj2VP5x=7NP z%08Erv48N9yDIomkGRZ#rO?SA=7+7^4^U$~U^gUuhmX4mY` z_uT|UYF^D6+((xEcKbLhQGH*6Sl0=_9cZ{+v78-8y%PyPu9^s>XgJHpvKuk38`?ZK zd$sqp!Mhs*LdMKKk}j{2akdID+B|2cHL9&SYX(M_)n#I~kB~9(&8nDRbPU~RFMjvp zLlfIwthI+dHMRvrf500wb?N*e@^whbIVJm*qhU94zstw!iet8(#6 zi86;^C8m};Ut@~@KTWIR#@j%Sc|R6(y6SVweTWq%&I5FnkPTpgvq)vfZ$1$w{1@=y zA&**I(WOn}vM9Rr-%KF&P8@2N7LDddtTLuXn~4yZR4?8peUe5#QdwR?T*#D_n7$ z{7;olH@spGydTwnjabVom4hyP3qh1NaD#NnCNiPZ)m3&Q&%%|yL39Zti)xHbnxhIC zRiEK>z4``QfBs=$>{`R>D+S`pCt2;;iZ-X;HwC)DKug~8*I$;Q`M~hNAW8Kxj=`>g z6pg5DT-e(+S}%}_4at@0iR<#3{yDhWicc^~i?#AU(AZD!M6hrnwl38I?Eo?hD!7x- zAi7usK%S=zZhWtoJ@qs7D=4JtwWm6S92--9td}Z$f2_RDxAUP+mmIXAlZ89dFky%Y z3mdG?Bq)AW2~UA_F|GGb$DLuo#)~1oKK$IMkX*^K|5Cd!8>2r1nJa~yp}I7qM7uC4@0Z8iV99LOLntGipXCG8q4@e9Ew z>Xp(Ve_R}!f#+da7$rHuwvzsTm3-AFZI`XnDHCN5uU-jDD)*Kth=;fx6yrb+&}MA1 zB%Ujk^^zf`3ow}OrJ5Bg#Xv|1%ASDZ2OlGnpuWL{hK_6a0e&Ma{TMr6J8&v>XLZyQ zmqKAUT)Q?On@7zyFSotdEeYZ(V}JR^!|ZF5f9=}3iG=A0{#Vm_xm-+{HOSN14I4v4 z!BBC#h{pe6qJULd)#Mj{O0i@}*T3%~Jy2@n{SKSxUxefVf)dqDFF1c>`)eP}g;}fZ zN8fIT@K>QBB>2djlJy~=1<5}#WXu- zU5k5=*WQtk+KLK!D#3pK1Kpga`2m@d(6GTtVD}NsN1w4fBa)F z`dA1Tr!ly?JiYy{*_-@utG@|8%CDCmj_^KGMBsX1q&aJh!?;?t$840I)~A&6dSrR+ zO2e+yn@lT~IYl_=4kJ%^U zJqsYl;ww{eoW3R z^R$ZRnmcLETX(qXIpeZMYIUCFZqi9fh8Nu~SoTqoT`vih=y&W{i=8S;@2EfFs?TeL zil3cxud|QNT;k;gd^YbDmMQI31$27znKHRayE+`lqHW->qLWdN>%at+f3K=4s8}h> zZO69w8Wc{UMALJ=MM1(LD|qM7E6nC=_79a`HVBE$ldLUQosA_^uZlU3gM2>SE5_C8 z#yr~3M}BqzdFWe(Um8;g{LO?9Bo@cnZQ%e83EkU4tPik#r@D^&Q7K#k1Ay1mG*sMt z7VTckABhg)DZ2E4+e{Eoylt%920Fa!fQed(=%^#0pZ@1x>B(f>iHeZwLAF`W& z8VXv6PZXr?*op{-xv8w5ys!gs#_>9{?_ae(zTXQcsP2m$io4mR!4>iZ4rD&>ng4qs z>MD&%<+ymTV>b%lVE4YmQl(^#wQYoU+(&*RPF3LPwQ zan&ZNP(En8d!XRdV$_t>qU1aIb`?SZ)?cosouqd`n0ngW_0SI$ezj^Q9zfmbrUK3P zx)3b7+(B4VeU8JeaX<@TDq?52b#rw)`B#dN z$B07eVMotP-p_#_DuVVtO>!@ZS0{oU_hjZm_E&;eEbt|75H`{N*m#0xdc85yY3h32 z!kCSdvku)b#1%7WY@{NTRaLp;OF7O`bJbJI1vq?6S9$5pgE4;rMbc1QP>!U88et5?;4r5y4dBBd8_)EVt*YECsNGSV zmVPE_fBc~b3Wf#T%X8A-FCLE)nGxM! z*s#DOa`@I+@bt~(9Zp)-N}oL-k}EC`eNr)5f4JnW)RQ4vL-1cEVD42aDm|7MQtmjz zeaT}quJBO~Q(QX)kYLD#DCjz7!XW0a(gnwkG;iS(1fSbj;X7p@4bL3n_# zKq9lc?HC!?nvY(~ApV<3Zde)V;jfKoFm4w@N+(FpXXSepcmHFfRd3UgJEs_`yq8ds zf3j}!>wAtR_^Gowe2Nzby(e7ViwgUZf9q8-v{1<4i$7nn2a8AkJ^1y(j%g#~BEjtI zG&ze|4f^5p*(GnqDDk*UF_6xQbc(S18l(_`pBcawDNBS-7(Z8kE=-KU?W&{~43pd@ z%~HWjq15S&V_}%;(|zRd(g|$m6);3$Vx|;oZ_*yI@cX=}!lvdOS29$Vlq9$Be-M^B z!bV=qPJ%9Ygsdts7cLyP_1bMrwyWbe^|#R+r6;N#`chG3-1NaC43W*^|B3r-%_`sg zPK6pNly%;DP*>lt|eu zIM~Umj&AnG^XHU`%LneQu;x7m^`1lH8Ph}=mQr8UKTc8K5 zRrW=LoRal|WGeoAs)qkHNq zk!L9KZkm8X@3_$84KOy*f90atWmic^N9Oj0XXl+qiL~!Hu2-GTrTXoZRhW$i_OK^G z+(*Cfca<#YLLar;D(lWzk*cY`^Au`=g0t)xZacuEC*;OCFZMwtkznH71q6ORSx^gl z$l($r6V#qSZ}?ys*t8Pjg-wA6OVdE@lWrUi7k?VmiK6zK0uE#Pe}Wb=9Z$Yk3?P{~ z$yl#s6CK>6F^fNslO8^@XayU)hlaU5lmktaT%NiLYxqRmevEUF7gm+y=#K8It+JU# z{^Hpc;}%9rT`7f%P|Ph%ASrlNwy<~t?UsXiT4vic9rX!+OvB*Hv z#m^|6!{HTS{N1-ae_6WG>YikF{Bq;jgm6HmB#-x+aiQ8thqAW7-cYwV_w_5mUSH!9;G0vuG#IG&n{FlyIPKrIcjmvYYn?VzO-QT;Z zRhS4$z@KC|f76mQ*(F|~Fz*GEC%dn~-XVI)EHXlQ6Mm~r=DfykqS_~@N!BniSp6*B zXe!)v^eO)G7HL3pm3c2OXgJeendzK=b-L1%CS%@?>;cG6D3Z-(mOY7vJPaEH%=v8A zAt3OzGOuNBi~7^kCFQ6J=3;W^@37UIbwTH%2)A2Fe})=h{%tboSQBTx3F+KBy=SZg z!uF_`gH&T(Di7J!fbN9z*!M$$_Xl`?H$j85;gq^U4i{wY=K&Yc?k-eAyZ&o-UDf&K zag}gpT;^EOPj)xT9+bGbNJkk4P+TiKEJpg^$h>5_SG6J%l1%TIe)h!r?inqsIL#0av*Z-OU{F3pz(F z(DXJa{BW+y$Uk1A4%wiZ=us*P?M69Jg4!lHe?RAv6P(}=f&GX-s{o+J^3saHN#H%5 zVaZAedWj)$I1=hR>IX*{=bLJ1FFCR;a4YhvL?WeLed!Rc9||{40AxfQ3Z z`ljb5@YqxIkiLLMXRf6{`Qq|T7wmK_frjo)i~@>oB8@Sntzh;#TB*z&mwA2%HU~Nn zf6F()th`Y0g0lJ|J3zPS_vwSeo(TA+1vh)VakF)PV2bH6FQR))JU$Y-P)}Ak(Autm zB=d7XBAdVoH$S8;kDL3|(tgBa#v13SRJwT_j!<*6)=dxuhY_i9U&qR1}u&W7^_e;u(Lj~EudstO#V92_*%EVfL-8dpy0rNxqy zIWuG&1LnWS6KB)Imv}xV*H5#q^4D%<(GaUPhD>+H_vzNq+F05@ZLCIs>U!^KIzRUG zr8PpM_$@w|yg05*Ju`V~IXaFs*-kBywbUM=FZ-z~YrX{{OB>aY2Q%`xeSlnEf6()@ zG7*YdS$u71Im08(7NWHQ8=>;{-5CJUdHMl9Y&#ZzGW<10<`!aRp4@X4+{>q2l67Z0vA z)htngx?;0i?ytlya@b;5)Xk>5(zIx==n49Cpsmv!)1wpk64n@JhB(S4a`iE!%lY_? zzQJ`=6r`K#x3@Dg)u#;p^~auiVQ5e@--=5wbl= zUsJvYT=~GFm2X@-Zo)J}oCU(n8z!}5OPt0lymA1$kYc2@hQ+h0A>qUrY>_(E`*y0C zpk@0awTBz?9k+-Rx=fUtR>S)zZ{bYui80J@D$E|$Mm2dCUso z-kAcD3qhK$KnNaFVViike>%%{GA1Gv=!Pp>#0~XJ&2uZeHXqJ{wn|>p#Kcu|$ z*D2Dp;+E24wKFP#6er8TtyqV*@a<;8?x()Z&tf$d?epTi(0C46F%@-DtiY)|_k>C` z%o5p59P#=MuE@ZU5Yilns%8Z53ySdf5@~lA)2$y_0I@4 zAxjekBvlOMjYtBfJyD6+GRykCvVS%eNf!$``#f1#t#%zls1I2A)hUB-YHV_p3qnt- z_h<~jnItH)mDcnPe6d`TtLh_A^CU6HzVRa3<^s1ecw9XTw!!v2AnENR3~`wAy6VEZ z`)ly6n#kzSY8&!9e+Xd$De(2>HropRR8qUbb`5pNsia{xup>>evH^1}nS}eBbB22K z1fVmp|>kf23NF1%{_t()&wBc*xoo~6QHqful6i+c5A(3pUU*=c z%j}}3>+@?NlKy!gDU<%Qf^<3s8{bdkJAW~7j1g6GaFSh9(bT5Y`5gfml4W8=bdd$j zN>}=;!7=+vf4p++y#5)1>&0ddZO3DMsV>xRXgeo_&?=KA!;L14k_ty&#+hCJ4}YC7 zhhwh)jQAF(_(!qR#+O^U%FSLQzSDXouYg4vtJGh4kGI#I#$3W_GPNZc>L$k^ z0{6R(ZyeX@W?*!9rM<@bX~%Lh+|bf(rzVezGKo(Qf2=CUgH=FKfBA8Bp=ff^Hy$8E zA1D>*ZHwv*I;ishX$nA9xAOXiX4`3YYK}xvu8)I5oyJBxFpp1^3Z153Tw19mjjx}r z0q?wzu>lqn;oklI?v$iIR6&)ppg_hTI}JxF=6LC$>-m*K4~E2uS0M%}C4FzuH+R)C zEBP7Ef7IN0Dg7Fg%l;8E`_>Q&7$lJH z)=tR3dH`S28_@I6CBZWQ=_7ZyOag@NmOl8qq7fXJ8`IY&Qi^pVPpHlqsS!$H%(r}< zK6jXqVa80W4r$4hJR?dG<3T)(?r>R9;}ZFZe;8k}2GM-R#;u$zbO_68T6lwfl!L4o zJGxgB^*@$KQJEYIMJ|jox8SOP=<4B}j(Gfw6V95emL9@c)@nqigsJcz?!ez?of@5U zvQLj4H6uIYn4ta{Zh_2`DYO0v13&DjUFNBIM#VkT z+hHh>;jQr)D;;^7v-6V>g>`c$>A*SaguX-9Ie?x_JTKM06m(6Uq9x4=cTKuxcf7)n zN;0|zc@tVE*{RJ1<)um51gN7^#BxwKf5y|&&;YJXL*IIACx8==^|&unMbNryb5M6I zK#YM{pV&n!a8SnlMp}6sYt1YU{!lgfqrdlNphcfjZT?cM4k&9!ljy>hZ@aEW(YB4~ z;R#n;{O_vml1o6VvmKegN({EO6_bRTD}O( zTPZt(twKs^0fjm&XYQ&gMoq(a7MHB{2yE-g9V?B|>9S}?bK1>T*hc{1aku;+4Zrt} zD(uX$T@ZJT7TvD?m-a76I;3G*f7+J%tLsjJ9cUrHfq@RvRc*pxTY-tZTo<>?#G8705=v`&MlIIAw zYMjJKJo&(A)quL9Pe~q3BY%6fe=|5p>@e@(OXff*PTrt`f+=j!ws(T46(M8(ZMEem4ttsAqlljwg~ zH`k~aMGhUYOA51E;(`auTbye6N z@L)OW&8)$~kbfvti;?>zXO5Moz8t2{c)2Y+Xv&5w$}^Oyd$QG@UE8G8&HlJHh*Wyn zxnEJucoJlHas^~1do9YV#h@DMztkxeZG543c;T81!v7yj{}EzNkAmZN`md-Ut%UV%2q3Ll53|~BifMu$`)$tX2bj+ zFXNW`6+2xKQ`1Z0qGZACYu&JWYF=z6FW@Qk)w`1(BDFEJe<3}U^23T_4X0*`YHWfn z<%91>3+JcQD2L5D`y{ryJZZM`I)+bv^Z=4v?;yAskyJUz;u1{|l*W|2QB}hhmJ7fP z^pA+HH_dx$fMu2`mNj}$1Zj=(;=_%zy`D6*a7{ay`1$rTusu*ad6DO)cdld>{x4F_cDQYg;% zE$o-W^!4X+Y?=K@EEhYWn-5dmMWWK}BSaP@CYw`GpaQ!0wk`V1?h)YG))rrTi#qVH zziRb~ld)`QDNTCL;%`3;A_`H4u82v04iK~5mWn*=e{XFQ*!toPdOB}EKKer7(Ze5a z_7641BTS{N<+p_saPqauH+5xj$ z&qa&Tp-2_zxq+)kIy{9d-N|`PNCUAjD|(Q&3+JDW&C>y9w!C2_ts~Y|l)d2cX;G1Kiz^E32(gl!77sM1;xo{k@%9 zHp^@{>8h?lQ_tQ_m_m>aj%=pZ^h^J9aK*+#rbuT=R=Ome>z^FmM=j^4@^!2>q%&4o ze=9fTX3|nQZrgZa4hB8r96IbDHQWLkLzjbh?p$^-9$N)br3?;IF4q&#x`%Ws_`wTB|| zjBm5~LH)*kW@c47iq6YBJub^|ci!o6f5@=MYL}3~V+d1dwGz!*T~l3EpY%7f;FBbu zXEG}PR2~VB)56+Ta2s466hht~)0f}(dX&+Cv-kC^e4fC0P2UCVDM3T*#-8?2780!J z18)P5sNcKK7$TlXHOlR<{m-XVU(Ji6ya?&P1bw38?>-{}8)~Ad47esr%oo3>fAJLr z1YzAWE#FFd2%i8!N~E3JGc@5gBP`IN4et1{z*nt}BZ8d(2t&qXrb>|ct+l~7TqZF6 z9QVmXy@zk;Y)?vfuy&2a%jh!`rq!TW!}idJy&vG`Yqom5koAzD<2}kMPmO7G^Plt| z``N-c`RlGX4*7BMoAvMO3v-gWe}IA4e^^Iys9eq-Rod6#fgj8~SW?f2V<7o-h~VNC z=F*^r1S*@uRHgmdtQUuR2q!gi^bI8^e6MaxkfGlPCDLFX6|fTD2n~Ne)LB=_CPUMU zJt4=C{Gs^)o-E;Ck=?^6rJi-17|s94FLl*#yX!9S>*jkCLCv4FJ!sEFX)SoL&;rYv4ktW@K+aXT%NjsG-zx zP+U6Ta_tyY^!_4v(#Aqlf4dLexCgqCD8PZ$AQnjmfa+2s+xgSImo~Kd_g_#rwg~r^ z-FcF#V4QaQyfOro$o_#G;V+A}#(!J^j@c$=Uv`zIJVITzH@g5%NGR)af2J6Jb8Q^Po)b?XF(4V`&a1#%;~1J#-1m#Wdl$L8S;Y+&2i9zZ!t?nh-9i`k zOoRH1v7^!1jh)*|e@BSqVf^|0C7)Woue$qvez)ZSx*Ra9_Ih`rEem?4a|eFOzpiZ# zQ*Q6&KJ--*4H-S&(~-|A`Pi|pjwg=)$ya9`6}dHJbbBLaX5<^a2IG-jK&0p#u4qMr z(hTc}qf1_6qA;s$rX`%*wh1w1zm3tfIZ}KbsCYK`?m)>ufBLJ^@yRh_79yTjqEVS8 zCi>61mu1`$)|niZA6?D3h|@FDY*p@LlNrHKHf#2mKyvpOiQ-hU#~*fY`d9*gMNfc- z1!z>!2((PcF>OA&Hf1vY%0B+8; zj8%!Vka`Fm7^y8(MG61^HmU2^o-D-KgHbk_|#Mk5Lf3!^?C9m1+bL&|Yl`leE7 zYundvzkjf&KQ}7sz%3KUKjtQEeDE+2nWzb$etmQFf75|hFdAn&6xOa(hd~E~(zM8D z@{5nPG7SX`#*b9v(OzDtH2v8+>4Vj_yp&PA`1Nq#sl0Fx{Dxmj#eG+%nPEFipt(;u z^WY45WyQxQNoL*>n1W{gI-CGv7s+~LtAgl8)Q8$A26kP}eaNrN2UJ}#vFQ`V?Scnu zaY{HDe?uagT%Q8aMQV!HUuX%IE#j(5(v*1YIVVvX0Vb<+#6vl%jEgI6#*q$kLV}-llO$W8a(4!j zf1BavhQTW)&VW4S4=27)+wDBt{;RGZuU-xg@hF!J>Dwi`%rhB!Bma~$ zTYnAr?j!UuKsK)sR-j(KHh-KvNgoy0s>2-4}ueHclV)0#5mczdZ(VZiJxU(Q?A!(ZR^ql=19Q{ zuXNc_vUu{2p{&O+tew0!b7ec;f0QCNt#?OSua;hp#v{cjV9;VgS*pKQD2eE{e_FnK zsU1h+)?@qJQK}d$t%d#Hl6>)8*;ZNZ7!Upa)sSVMK-`aBB-!otL`X(I=@I*oxPc;k>`Ok<8nnbQ zkpBlEhM#f~gbVZ-ZW31z#d>{vOtF1LM}J2`-l`R987x`4ZOZ9dhDp)UV-0#GzNqro z*t7_Pp z_rQKBHCl}_z5nBGMTXJr6fEMwu+Ne4F_jfFyO5lWTDxb=6}w2K6gb+ zJ>Z`<`Xim^D41T5q6#ZMepM=Mfir&CgQhu0l1jgvVLRwO=3d+}QA<+EE#pS?7a^rj zfa358jZv#M{C21xm*9*#cGgQzM^WFFN?jtw<8!-2eWH%tb2;9qb--HZ3rCu|WcWAw zWJ%q=QtK}7`PNcc64Wmj4}TjyTfS>(kBsaHzr=m8p4xAkATY7oJkX&LR+-u0s>6GI zz60!!d3ttJXCpc@LHKefUsMDNheU$vk)dHvs@lr@K92?Wd+Ju*5V2j}TdtbiKRYIl zl6((>3og4RF9$<#VZqK=hROsb-Od(aI)yQu9l4UiZNX^5g$LFc_J5Pf+3jL=YZ*4> zI`q3r+kHA14ab_o@k6{#3legE`s6b%vyDQWOdO-yHFwd)7znUnNKX>Mn*b-$UXXnW zW=N8*bhHvsD+F{?atBAP)ow5_N7;`dMYh#h+u|O1HF2-`2ABCnR zxQEIL(<>Sv2y1}fAO);YBuz&{3$ypTyfQNY&dT9)aK(Jc<$ps2U}q6WzR-ykD)pNR zdRx(F&MmYxZ%ateX$9uNt!kI~hu`G)(p^_XB`^zVwA_T_lyMe{o zGyKd_r4_3udpX`KL(a?XIJv#!XMv^%OqwRQM<-{|OVi~aQ5+Y&ic`9Bhx9>kke|q2+Ui@yPSKp;;WCYBE|=?76vpQ~G7usZbdr)u#OS7YwPuXKPFTi6LW8<06!` zb3w{*^}fK6*akB~L7faPM?OHx8{|3ebJzzJ73u%}p%to0kU-&I*nemP<>?k;Zf_?V z{IPiJ?SC9nsQ-e-xibN%Dh12z%}xqMN}{;f$~+~n{S{dfHPt1{O*hN~`>?BE+y~oY z5x=1sRO)xxNEJ1b;*5Qj2HdZN@-ZIvM)m7J%9h7(g~?uBM)p|=9^DcPFck%|unbyb zWiA74Gn~p&0Ok<;rJ0GbF>cbXnMJ<|66-04Wq*5jVQ$X^nE{+koVf7s^`4wyDU!Q* zq3~pOk8W|{B4OvmRDl5H0OhXcu^Nct(r{xidu&J>g?l5{WEh$5t$l12FH{Nv^lB-s z7HaE>H0)dZp$}lErh5y8^QQ@yLbL!_`NCD=0Jd{ELIQ*C2#Dsc&COdPOdJcG(H4wt zNPjC*)u!LH(|K>@$Wv*Pd>sNV72HiK@+LDW7~nQW;>DA1=ctw3(&invaU4VDrx1)o zd>h(ghK_*56(?+FA*BHPpZc1J8R{%3jph<%RqWmZ=YTt4fJ<4RE3MIbUF|~;Q(KCI z^4~j>4Q5cOHp%-REVJ2mruk4a+B;f0J^@)Ar-9-I0BCT+4{dekU3 zL@__(KpfM^59up7ohcGjwGWGQZNHDzHDA`Z>%9{Hz0%n4goV*ra**>ADqT9bQsc#c zKC3q@*WjKX5$C|yCz?ozRF>7FTz|5Gy>&^A{7>K*V=|i;hD{}~L>7z(q*wlwMmtYu zcCf29>avQtGK2=*rJjW z&?CU7XKSArCh+Sz(FA#n5%nC?j31;_Uin$1sSJG$=+{0c=(UtoYfx$J;(tCOg!y*X zg1FzNnJ8?Zrw8?^|HwFPv(Fi7)#VoJ%NhqFLhj)WF(rkmQogZQ=iF6pFC=ij$dqg; z6!_eG71rQp-^sO!!IzdV6#{Hrtb+dm%%XWmkv{dh1*zDaJoZL`KUHLDmhD3l;TP@L z9fMVxMX*$rv{60R@COXO=YO?w_YK&>y zc20jd7EZ9$z;{(}K7SEPL#VY`V5*GilDzUJZ)SJ|Al29`?SFt%rW~=3Jm+GSgiyCq zoj5TwEAhz8xDg?l^;`4^62EV>s`M3<~uJjxo6^BIqm5qo!)P)e!ys3a1f~?Wxt0*;(<(z^KxfqTk!(77{(szpe}Ys2R1Ah{9s)_E2u@LL2nkMoh4>w7f*ma8&2?qH#R}{}bXt!B%YH0llAPFqcYN%L(20zVzxX zv}V2?P-uaISZS!TM)-%{2-esyq*5;(m%VEJ@<75McYk(h9hrc;dSVFTt1p-BII42} z7@h`|qe0U9vwNKh7Eq{QVyUtzx;^ye0p|PXkb9FUho1h z>l3Rwj(>W|DEtS-7D^1R5)N>hcAO_66BexsK2o`?=9Z>@QBh7n)iE8xhL!Ev-`q<_ zP_TD_ixV^1V6{efT_mBhjAMk$+o*jrJ1V6bBx3G3aem$ zC5ZQe`KIbV8C{3mCFTW!l!@)$`0dRy3fymop??Tok}mxDgy#0RSxKB0G&v~@8Pq0>!lRMEov|qA^#eA!RHnN zeSedK**q?#gr^_|ZPW#sf9Q}?U!K8vkvI|FGRJ;Ge6jvk<#gERzOPv;9*Ykah3gne z8NJa;wR()9gqh?=bWFwgoUESnEm6w59bQ<~dwT8i7aNFba5`uHCMbOEvq*tdDyXpL9O^hoNBXh=){0kL$WG_UL03nsJ$3w)ew}RpiU0W14ugQy8ew`K&6};$kB2op=`y0 zZb>V3nZKU)bB`2EzHzcgLV4V62Wri?v0@LEk?dl|r3773>hm!@B0mJ#D4Scp27hy4 zo0x%(e=&Aj5pn?#W(yWX2P@fhu*}wpZYMV#fu`bxCI9FuA>JN4jZ){n7-dI45Kvnzdo>OMpg30lD$ejx1bU)v`lSwCVy$zbM{`I zIu+LB!zzVLN%g_kR}fDk@0+Dpb**b-Yi~?Bnh>yS3Mb^Ip!e`=V7xeUIX!}C!M3NX zR*d940DUY>_ z_QHwvlVEfAxom0Z(k-CdYZAvK0!V1Dic)Ai53WUh$klmCTX=7|S0zJa4kc9Ero2?3 zf&a`2BZ(Ges^8Z=K?md9O&Ltm2fC=(Ef+ak(yS141Pf-wX6uTU-hWb$;MS-H9S+69 z_&>^_ZjUcJtdf=9)g5za9t6PRH{4nv#GdWPJmQ{FQyndk%=_}rKpQ$9m!5enlvVw( z>y6xtLRyjWAIfM*(|)JrW4!-A#}88uJDBb=pi>MVS}q@f`Sf+gS7F86NLLb51KpK= zF?y_K0Xx{yg$8Ok&417M;^Q4yi5ooW$qNTn+7P@Bd!$)wc;x-GKQ##_{^$pU2+4l{ z95=?yfpUJ;=)u|{lRv{);S4?8J$H_7D4-6u$Gx~M2nm}`n`;M0{qtb*F#ML$67t$)OP9fTh4fa@TH=jCQ3 za~<1I242FRkeA`3m+1y%5cKfRmtrs7fn)Jv8>|x_L8lv91^kJem3T&gA6` zj8<3GJu$QUQq;1A`;X0$x*5y0@m3Pqp(Z#=7=$k>U*>S zI!!q=Hb%r&f zd}J&nro|}L_xVcrC8f#E;FFaIAUf&_i2e*O{DD@vGJo?bBERqRgPCd#*J4v@NiZA3 zWf|!AI&o)GV>$aot#!?8B~|ZAWFoNOt+YJ9X`s>h#R?ca=p?^rYMVte4VUVX;I5ISE!DqoprX zn7{$?FQA}&9Jgdr;eCz|t(y9yVEaM?K#WuNQ&wfgcKrC)FMn-=S-;8Cy5oEG@_1s! z)5^fIb0pc)aqz@5gIeQ9=c|mQ5yJ%E@7V_6AAdsAL`T9oi#lJ+A2a_k`3d=Rek1dl=WNP~fwen>lQ!B}b)E^qcDM=}P1% z&Mg_mls1&kzHC3pg2=fuQ?j{iCGH9)$A=QbvxD$8>pm-YQ@=JzO}YK$>GLofhczWl z`+wNL+)L(8E#_Af^y6ZQGHJpz5QTDp-MUx#sdSFZI zsFzhZrDC{^RM@j}v3{`bJuSV<@(+u3TpHs^bP!+_Mc9gXtJ!j{@2-dX;149w{(4*+{w4lZfbe@_jya1)dSW`D@8Dh7|t{FG7#wOrwsioMv-S1SU>xghww z`?eYP%41yWxrA-7R~$Rj{kb=$0hmz;G-V7kJ=yC*SA+!vKTB2O=LqXRu^X;pO_R z&V8(L*%3%$H6tp`sHnOfr6%8Vp?hWGPUUeun&!h)1FQ?C37M(v zuTL$k)r+?x`@<#xbH{k2ZZ1+9L&q(+dWxSPQsHe4tdjFzPB3(u!2PQ`4}gFiie0T9 z1sUe5GvNcaC5u!q69B!(;(v*WAq7*o^ZJS$_<#&$30+ zcdGI80%;8Wt&I+(THznY`nMo@Lx(zsYycXH_4tlM?6xx%&};*3$-C4BVHQXLYo>3P z%&$-9(qQqQ6=RFK$RmFMBs3%L0ELJPj?K~y35Dj|zB*C+L7ybb5dC+qZeRx5fv9+1 zp?#YsuSfU^6?-^c6rJ4#+JAAxvFtQHKzeFUvGLO9h#|0&ftx{5g~oz6A~6fjI6naw zrtU*%^FkA2*rA!}gc;d_xMqE`)&9W8Yzd-QE#=^}2<;;ZA^G$?$wSrLuulN_byVff z2$Y7iTvf&&O?%@D*pA?NtW{KgG6gCsl;4|ApO|ycl)Yqz-lOW!8-GLuWj<%B`O&qI z3jg#u#);IKlf`q;fraxNPGz3zd;v2q=B<0_s{dES*w^PkPSOz#3`ie0#C~beyco=> zTkk~~CHJck2`K&{bS|sXY+FaT*-pWSJWJIT_FAl?`knlq3|ZZyJ)ESsmp@*TAC?OG z-`qt(raIs9`L=eMhJOH5=|_$5kf94y0ALNozqzBWDdjZ<#wX!47JoS5WRsjb%%hPG z8xj6mYzQy7)hZCWS6dtZ;9NRW`bU1KrDroVV3$bdcly3_&+cs>el13PUu23EQnf=^ zNbGhj?xy(++6-l8h7QS1`j)VU$t3wjZu_F0Sv%$n!uk+<&_%?P$c9=Pd{KK5(Ww)Ql*X2160oq+O#PS`qsKy)^+Is1r%8n+ z1MNN3w|cc5*4ZtAv9S7Nzl!gsXniavZR;F**je!W4eWe{qD0u?hxrX;eU8$?WBQe+ z6k~5T6;h+qY=5k|%zu9NU1OK808-oJ!v}xlZ7HWt?`PE~+u4ewN-)fgTIic1r8O?M zLewB!9&+la7F3YJgo9TNjm#C2ejhww&_FcoRK*_RJdw(hFqHHFq{;MIZ^8WiFk$)R zH{dQ?W1K576T>na6gZx|5MU`S&SrY(G`Mf^1)JTj41YwT=cS%!wf$N;^?gD*Od2ub zPT2pF7K$a;%?xVe((3F?v2%GBX>t6lj&o=Oi z!>qj}E#B{h^U5^1f@W7rI#p$hy3WT6sh=>(#1r2$3O->$D z=(fb~#}>$|{a(avRyp(8_gjJxXfNi99Iuz;QGbG+n-@n~+R0nSqzP&4~HF}2$`vD|k_^w*rD<(!;Ij};9J zG6ohhHS5F~sll?Anq14g_&^_gT^zj))fk*s9F4pGpY*Va=OEE9)A^F3F|^TN88}d< zdb+jrW{9_zUi>lvKo0gHplN-i2fwnocTFKJ*uU;3xY+4)z- z>sIK=`mD9I+_W)f;qE8Q+Y-lCel*%9?DpbAkzw(Y3znvrbA?_g%inH2K4+Q<2n#wE z=(V98qo_5os6U2SeRt~1XZYjl*@@`ezKl9}nvlL(m zjMPe!dc70#+Snh{MwL~7orsue>I$qlKH(5GeR9CT}Ydpt|8AfotuM34ftp zDko?fpUcYvp@|$*umLR$W5$$95Zn$`{S3ov`e%LN#|cK1f@!JI8!NN(pu39KPp+o zd0ZaJjbP^qTA8p@v~zi1#)V(@h<~RNTd($2ukiB$DT8x|=Nmoi=bBPbQO&_}8I?_P zr^C4~cEXDVDWE>iPmiKn?^C{9dP=og(O^Mbt2p?h>$^WY#5Qi_*@;Vo0~aW5T?=`u z>QFpai9N=>8=DgNVwvnxuzLqJ0V6Ff;YI9pK$TEk;Jub0rwGDf!fV~t^M4_-%IOUp zdwnfTT zk-7BNf~8mxDRy@J45{E=w446l&Q5zoFn=_3i#$mw@*Nu8 z4x~6`0-ih~5ZJkrgA$|vgB6c+Bd1(Y#_xF+g2l>jDAsQP(z6CMy^^b%jgzw8%WbRI&@Bz@i;9Dt`#t`b>XKc~hWH7S@Mm z&71FZjMkEC?oBF*FyE!HHV(2vbH4XING=|bBqgg}FHv#`6}L@=v)=W;MG>JA)i4p0 zo)#u^kBuu4n{DF!UCh4n_^mOe@QX!?FK)p|@9uH3?EQiWrrrBGK5%Q%P!siULbMk~ zE}ci3dj6D#8h-^5CQklWb4t{=GRVeb*gzg55*aiQ&+2bqN)VPb{HyC@SX^sU+)vo< z)3u|H6|K?aIe1Nse7V>ze9;8Ht>Z2<5_@x2FvZezL0GeiV+BteP?48;cU@Qq2(+WR zCm`xgw8GOt$Pjn-P|&bfm|9`JPT)TW28oJ09e=A&Yshj&k>_=u!JXM>8&qeC zahamc1@~JIhC)izvb>W}8c$bB6GJv__+Fw(Ud?}NPbRP82_TZMMXaow7qF^;<)2L2KIt9Ct@Xj*@=Z$Zjn1Hra>X>K<%J4l{{O$uGOV4W}QUA3II9 zzs_HRaDR=X*m=NJ49Xi|bcpYATD6xkUY{jhF2Q*9KMkN!95NPm;WRwt4w{b8qT^Wx zxK4vLwRaf4uI$I*mFbn2N>!0nX&DU1pG$8DbNF-IelNIXxPLXgTZXU*=3RDXQ??60 z?TR0V?Es5;VyBE=eHyX$3106mpFegy~N!}=EKiluxRNW~a^D=Tj#Y&Zg!75o)NMNU#4h^Y1@ zQ(%a{?>TU-!zznDq3MZNkQHpEpn~i;m(YkPx>%@*WlTg>Qb&i?qE547ZpQ`4@td_zf{bmi1di_6_(;G9TZf;s3A$s z8LofAK_?>tLFP$*meqA*ap6P zUx#R$`36`~$I_KRITH<%HH~sE06GyZJtVSFmxDIe!2bq=mw#>YcjJty9hf=eqrfo>&*JNA&s8W~IM4 zB!!Bt$3NjeNAs{x%?WCyl(G^}m#uW(0IIdUBGl^rz@+`4KUtUC*ZrBDFVAEEM_^`Z zJ6l=(EK$h$KPJE%ZT?C?LT{_HrS{jgZF&Kn^O3DEOllCE~WuUypY#UucuF7=`L+T%3B& z`_v!PHfo;$!6YHsdU9GW2&6CfZJf@48lFzA@0`x4WpnE*-mC56TSrHKS5g3k###U3 zQ%TDpbrD03tT`}}kADbr@$3vKzP^x5x*0g}FVn}S*{$nf{v=lRF4b<`>?QS(vnzg= z+3#VQ3)>0~b!qVEzZvarRDtbIQJGNm;$)Vr9Alyl@Z30Ekf3Blu&7_BDCaAOoHWEq zz;8^jrI8{julxBYMDq`_0XnBxaFU_a7{iCuZiq_Aw$!LHfq!5vp^JU{C5uYk()?Rm zeteFQk%RtZ82p5~qb2~MIQ0b$P8ItGbFqb$4wmmme56zx_f@HLP#wl>tXi(}QIOYa z^08Dy4hiK|?j{gmvi+6xX9D{*3?AiaIcoUsIi6N4-gFXkAKhG>PzJrzkDjKPg}e+!@Sp38U3ewgF|7rd`3l4*mT`9SV_V;H~a9j zi(7N^jwMAxXQPgBa+6CsCA(|h%IJuOc|UA&M}K7nQFT^ELUC!i1afmL&fGDO@oDm# z`$71O+zw*m&C7zQ71N{nKYo`y&V*E3`4mcd2^PCe%76AUy(V#Nz3c&6N)xWA1))uH zRe*lsYU=JmJH*_i!1{fGcy=`b6&S^R1Ak%1+tjjM;$WrMKrWJdl^XRDa$8E`qS_j< z4w;*@zF6wL43C1Cps{v~Zx`I-Fc%C7t74f!`JSOZ-NkOU{1y$0bcZag~bPc=0E>1mvp$YR85M(=1 z8+x`UCID=wfcgQPDf9vfd7a)pI+C%~sq;Sk&@JD*Vi(W2T8Aw|D($7Qf`ii2xmxvl zGCtfO)`4ZI=k-F=drBfxJLiEx6L9W4+7rXBslzxR)3@T=KvR{=_k-O}UBK&0Bj8R=k`O@937ELFXf?~ChD`7pdz;F5( zHb96vV5U<7M#G_#2dVj?nelOBQ zSu$7jIk`NtV0M9!0%iDbRq^ef8%afFTM^G3rXy7iZLeUWN;v;w&~|h4${%a%{#~Z$ z1pGc8G7A~4n=$xB9N9W@=b{Nq5^w0paWvoHTh-goz%mWQ?x5qBwuu76x1B@ScL-Twyptrxp9ic3y?Xt}&M#9A7vz6?Sqi)rO~ z1Zo&^sXWYWbofWslnXOkekBt7ysFbvd*4wEzKC-AFeB^CcK^AGKE?xp4z&XxXP?sw z?RlINE|VA20Z~RL0t5-IEr{1#IyFCDnZJ4YB$1~PmY}oi|0}>w27_@|fn1mm4K5G1 zKH9zC!Zet8-*-hKrGKJWY&^!YzSqR^_bZgOY=iXi2Dt5;gPAmq02;wb<;Wl$v5y7P zM6VykJkz9;RM5W`L*C8Xqgf31-?f%(?Z^CCZQV9GVDG(-!y5x(6{Fzs?ue?bSoTw^_OyVAxfX4=gQ^u<(prUR|ZEHj9<2*4Mj(?+R6c%UH>MD*x9Pa~i zMMHn-Bw!0$gkkNb;ay;{--htiB6FK@k?gAMqDQ$_F1zWK0HH8=InFZ@z85K+qkf^l zjVM6GhX|;f;LOv1&|==E9mD&=w)HE4TWKa`I4T}xw2#;Qk6M%!B%+Z!)KKJOVCa(| zu$b%7qNxwyNq^dF0n)J4ijjaa@*#xqyqDS_c;>tpOz$gWVP^P{2Q(|${}He>zj*-A+r!k>!Bana#RX%-jCOIB9t4!vxKwlQ_FYHv>X}AzLZ*Cx+ zN<%R{(rPuJ9g9|FPJbCr!}YY@U<;>J)><{7*z``%^?#R(u-z|$&(kJN^h6A%d38>K z!@^le@kte(;Jep;|5fUBAfJuO%&cGM+?pMoE@K43BJu%BhUy^LUSA9NSh}$4^Bmq_ zw<_g81%HDy?wi|})(-5ztoW_D5V_&XcmJxAvr9B#?TolovuZbr+t)#d1PA3TfpjW4 zvn6(Ecz>(_H6$V~-qma*)r?Vtw*cJX*U&=L4l-SqS^NfbH|_cJuvAvnexa8yHc1XK<#nRR@={qp|(KcFOwnu;O_)^eKsWVS0$ z%YXbJnM^?}eoAS%Rj*c8hgGK1gsf!k%Y`9$MB)KjDju(LR;zsn{N-7A;p<#ahZhny zy5Sok+MM_Md}jRUEiKi8r2Z_dx)bsnp&1(Pm^woQExzMB^XuWA?-?zd3h%g@OK4Y8 zXTpGkCAIh_Lq>U3Jh)~G0_T7kB-#p{a(}v4wioYyh;C%ZB$~g|xro*pwJkP41~o8| zm8lO`6ToTM`N#_Kj37<6k_fUO2o0?%n;N!pr}E$9-*Wh{zOW?OXYef<+^o%Z^oXqQ zItES$q$O@)RY0{*PmifSd+>?>Bfpx#Ks%lrC8}g=$KOM^>r4Mj_-_{czcuYcwS z#5;?B3A1kvYfBAVVm!C?-*7YMhW!9HO6pia2P#I={<{J`&Ui}@rZ#%-MvQ)%XhK?y zA?ygJ22xY(-&IyDoKWAW>xIFH_9^-23(mtrT;fM4IF)s-=980VcJ)+4NKpe+h>A!% z#J#^eOdB*FbPK{+7drBDa9F+~*ME?zBYgm$Ax+$GHWgPDgDdfFe|#lqXs#S!=>&#_ z6Syh>mOR^3@-2*PQomY*ZCN?>dNBDWk|AGq2(Lh+7uv>@%v3${-O$*(0|?59u&Tk% z0&_j`ND&9hKgIpnn7*XdC_w4A4J;WHM|;oYCaHsTw|aXQQ|TVM_auH3Lx0p^gabS` zZ!eI`WO|lgyR@{&EL*bo2P-ZW#d6N}0R}|{bhJh8CBhiA$NJ%*qnlS41^C=lWq$pj zYy-7A++o(Jr$Bxk^?Oq4U|=*Es&PoYe#ag= zB??GfK^j!{zlUC$ACr-dLVu`B)tkt4`5kN2Vz(e*in-G3LFT1fV=-N69I!{k)R%7d zY+2y3;rW--X2@7@-2Ah6FSsMnoInO&&Vk*xjVo%X)0;7Nuxkitt@;Mx;=o$+OrLh* z`p;7d9!0{h@`Tzjo0-nGz-{~X1VaI>jjPiA&WJ8&350pay>gKf_BHXK&718&^E^M-D0rYdeg)n1K9R_C82p+qB$O zX-}r514(EuOBl4ig>ps7IJcKJDaa${xvL%DUkcUV`^*<;^?wcuN#)=FFK)u~`x2}; z_pj+%2d|Dhz_F1=iIl_>J?r3w9P@$894zk#8y48AxD1K{=eN4dx>PWRf@_~L*9cpY z*SDof>{31%LAdiXESaKC6Z)3|6(l-iW6DsdjtO=Gz~Lv(>BcN54)bon@x}=L_YYUb zp6`)1BJQ?U=zogbL#J(PPHGw6#3OJ_+|_eRPjNcaSUdfrvhS`b&^wY(jy|-Xej3PA zCeTQWIJgDFc+Y-1H(tA4E{^$YfXQJ^+Ky8h%IimDyL=M!1*;BnHm=T`NYES$`bFw; zci>sVjVKmY$B>k%t-SOa4bO`@S-tcGS6SRgrjpTjh=0e?<*_+M^_?Tim9yR^EgqM8 zk@_M;IDjKWwbbQ=#jE}bbc&VU)qviI!8=+TcBUa(bV_sJaT$c}0njBB7*2dgUbGv7->RCoaI9HjZ@!erv ziD5m_4Rmwq28ArB)lb-%(v2^{C_bVLd>VXDBa%uU0*>^G)JP$udpEb zhxVjf;dGeZ?0B+F7(KU0PaKWSV*s_GokY^{(hTH$eOgVly#tcjSNjzw=eZ>qqJMe{ zN*SmwUJ`1kwMZojJ?>WytS(vCLFGWad zDu2RZnb1t4@qB=fapo8(mR;%Y>fo|PiplQ_j~3%_qD3BDW{0_{l`;t4&|o=zdN9k| zzH{n0XL};-20Ey(_%}XnH2?HY3y)Bl@&M)(PP%Q3+<~A7GSKH&+dNSey*7&TBj!42 z1V~d3691InfEnqkc$wE6HaGO*)AfP}w0>PVkl>^%cT%DfVVkA-2lZ}-A!Je(5r6YB zun~UuLe=vHp;lPeE_=d)+K8*;U$h-J4haj&OG#w7e??VCBdX-&_7>C|LE?X+Vmshe zVV08}^Y&+(sN`SJKEy`Xv8b=k1@hKG%V%bD`I}xSDUpcY@|9uyn&p%1NVx%WbFUf6 zGdm?20vXu4laOUNGCNyDwQ@AD;D1Vln|IJfoTB*re8C!QT?+;YeXXlx3nh``uw{74VeflDT|T%}tTr2!Rmz9#!3J3HUN?a@Nyi!M^h zIw^`59EhT}6T!GyT8L_M_^KamOL?IO!69P*Rz&z~F1ar(hCeL=e_nHCh=1v`z)v-Y zTMa*;`r7sZx&&5oUK-9rP>W1FBwb(G(_zJ zoD#amcobj4t1-V!egE}41~GNV3e~Z~^Ct~M41}m0?pSh(Xy_4D7aj>?yy70*Fs4&* z1;95)M&7wKuYvF^cb2(@<$uy#M`ZVL2Wf~JbmOXxWZ^LfYmoY$+(k6o78}b|IRQ}3 zbVE_M1JRD?gDz`EtwzP-8?;E)pVdnpiVj~LI5m0W65&ZWJU>=KDPx3kE~x#HYYeYe zn{LvW@ktTdnIEYBC&KCtd4-P{H_Oj4HN4(Hg*4-`ONm%!Wp=KmR)1?tm!`1`SR^u? zD0ZFcZxyz8Eq=5!(Rqdscy7hgFB9G($oMe4079DCVg55? z7cMKW+!wJ^)z=6!wts(sd}?huQI2@&{n&J5m}E9)Y9A^f_&DJJZyZI~W6lF!@z%5+ z@#iYS!?{>L=T63|=Lg*{o)3M@#%zl1Qebwy7#Cda_b9VA;$P9Sv!=|+d{cQ0diN`k zM5H?iqAj~hV;drKmy>VFQ!5OecxCm@eikFNe@(u^qI+<#A#?F-NY5=f^%@JCt% za~v91WOD*yRSvQyP#kUxAJypyBz7GOvot|2Q|3bTZX74uPi0S4Zg=R?(0&NuBHtm> zt6O_s)I9?{m@nNP?yMR}rXXg;cTX=U(n=jQ93oGJ_pT8UsYe45^3;+W%@P{p4U`^8 zGPWdg0isfz$A6H|(U0vp;6|O(Lj*>ORhO{qb6#mx>FvgP2t&kN(n$Yey~Eo!yI>Hr zog*{3O=wF|ms}>J`&(KINlU^-%Jl-iX3ZQ~9><&0Iszni&~2kdy$IHJGk>QE27e+ma4U8{qcGsRkaXM7M4lTr zVX!rg>{LSpe5-}CJ~P!D^CkRc>+KpsQL7-W_9uAkxrzXh|0wt|WCLMZ>J67G^*s}% z`JjVj=Z;S6A{}6dM!m-n#84j=b^A&J#*@2S+ynNYoP&s!rrrsH$i`O#2XI;{xOq69 zIw0Psy?=AkT0cKD;mTPhdk87l$HFo~Bf-8xLq{92U~#t4+1J}l+VuoXSm>pK1c?gvzX0_A8?%JN3^}d*i(jv`_Nfh&f~~ zZ%6pO&GZ4<9zqJlRJ($)&xDzZ8|F*f3R4nr$5G(0gKKD`Y~}>?{Fq6`An{@iY?i}Y z7Jnr+$fvvj$z`3bbt^t0fL&W-7`vC_p4&>=H}4QHELn!0cHFzR zd_jGwnS+=O?wBh+LzhctWKhxDB4OiF9;JgG)@9*ig#$!33dA}n6g#m*S)+2cTOr|C zKiJAPU;ui*D!Sv`@+)WV{{=pn$y{0?EQ=LXjhX&!HwR6n??W1x)6@D!N=q>WjDKZ# zxR*P0aER9{fPXr)u?z^ivH%G^4e~$JwGq=ihP?!9uKvY|^h?IYI;$ndJ=|7_&t20% zKI?Nsd(kkiGPD0P7`ZIlFmB$?e9Df{R>KTO~O*I<(y9WjLn+OoY=xcvGUHQxX1|B|D zdt{w5RY&?fY<>#I??-o==!kp2_AJE|OCZn7>UP!nYh~)tazkJhPucL=7!?{(Fm;$L zEy%J%7cTpFX}LR<-g&EgyNn-+975mlDpMGs?GoY3zU>=!DJS9D%ST|4;D40Nx}1pN zGcIlk*%E$=vRnLtl8kf2%IfcZmW(Pett0$U=v-`{DoERGnQ2Q-5oA+d|9Yl{ib==Qj`K}_k(jluBDI9QL##`J*4$x<=sNYHW^vg`zsq7<2|> z5GEx#zpaVG@n&h3UuVkV#YL+)S{nmpI0~*69_kKxGWonf?mauSRDVi{3S25SM}0%l zS)zJZ`u?fchA3t*JctF zv>u&aY>;uSSKfdLpnQOTVUXa42n)HaSt$0`0omcD(L10P?SJHUa#I%2Ah$)4OIZ|P z$3-54U08vcA4{2KZ5BdRPBSd9CW(vvtxRz0T6QgVyQlprJwT2ZLbOShA65gj@)z@Z zDCx0nsbZ-vXCZBBZpV#l(Rg{BuJFWqx9=*XiBF$x`07ksd>$@fu{i@qi%-g9bAo3O zijzve680rfdc9j5DrK2vv3WgcR6JRU7UEpG0jyeYFNQGf1*c#@9x3H;xU7XkVkEC9dN zidneN9-1qiBA7vstm?$bojVjdB1$4!{d_@*r|l}=j!ziA|Lxd*r?0k5mkK!BF5+pe zT<;!>WAOI1vDah>M0(i${IG;bfGNq-EA_N1jz`hO7|7IbTPeatBWLT9ow;u2=6oT_TNsq1$zm4;$ zY)fYSJzl)5mEV~vT#bW%pK+*l8xVBs%gX{_f`2Ox22km2<_75*gQ^&t2Hdq6mx3Jt zKS030H|-nT$xr?}AcJPu^xE7Vp&Z)8@3g7rNt{_3iE8mH&Ogl>;_v7mK_i~1--fzO z&1|9A@LLZE%@mAYHo&Vgv4%_35)&E;GEBwv^|-vk5dFa%cNZ}_01{_dHNWU15CBrFl zZpR~;+?v=}#l2Dj_TlTzmnaVA@1ph2sJd>D+(m1Tl+Dzm^sD)j8>t&xTaR!Z+x)48(GqkK zsv3VSBy%kmlG>XlMd_417g}Bv;Fv z0TO49iaQPRM#&AJGMEgxlR0fA)bmz;smQfiaQ`dP#F7033Z#S7GlqE0UbcFM_H>9l zUX2Y&k=5GOu!eu10HQuF^Y+TTC+g*lMPPrE^lU(%{l+TQSukys@$uuCc#FGnjA1;x zng0ExcJBe~-VdoLbd@mw!(p)w{vwxkaSc5r`2jX3GY^niYvb0j32OTj5611Mh!hjE z!9d{F4>kdLg8i}FC>{^R&q@~$E+unAsaPa}=eN7kdih30I{F`agd1A624)Lo7cC^n-qDq|8{lV`W323cf-=6Q{wCjwk?Nb{)sjO92L5`H; zAMSKePBM1HwJW6rv;Z7Aq-6o~5Y!GHmCcZCdCw4f^(Usf&o^m7rQhzVpim85?_i1& zew0rd*9L0UD7;DwOa!rS!z7LQ+WBKA5j8smBCfENzjf?e= z8U;Kc!k-AMFKvvt=MrlVTyhtWx*B{2WPbum+b=q?E`QxaZR*E&AoR?%%sooKXdrlgg6EXrtP+`!h*o_%sEQWYa*cvjqzVYO8BLb79CEhGbCFQ zZP!L{_|PUz=|O4Od`l_sCl%()g(;}z0r37rP=?zgbz4o3A^X+PquLM8F18t8gZs1_Lh6~7FJ`j)PndWY^~#sf-nRdZiF>UT>XFGyg-`TpDL&y=C2+< z^W`d1@_BUAZ316O?QUofJi^&UM3J$9Wsu=vMEkmk(cr>h`bUqi00n)w1N}r*Conw` zi{oNYaTYjvC_Ho)D|3^hi-;t5t4%bz4I6!ATFNR9M{bXpo7kzya+o=p25kY*gKxdX)6`9 zz0q&RJ(_J)ur3dWhhy=}X1vOUY&`-O(wRy^wUuFBj$1>)&X{9|XX_`l0z-j^qbgFNEOm>a3mN$P_#{- ziMx0udBx5Fnd&G5;WC#Bwzm6kx4uuFChqswYWjcDJ#VX$^D0am>>vSCq*zjH{oIec z5G>daBVOz_uMOEE!KKJ}f+3tgH(}n|Zjl*GB53KZrxP91GzKj$d5J~u5S>G90Z>1? z-Cia4^%n*1>4blGqh}q-iySxN>UUo(qwVh7aD;c-xb|O&Li%|{XQExmMyT~qGZ35( zi5P!%N;L!>UR2--7_E)-Or9|E%M=K3l#D=!t0if~AXa#%rqgoo_gqsdCb1z~6)dFY z6plMIztb_rV0ND?GNruS@x#9-LCtw9+KYXiET2_36FZ!YFXTYhyk&F=7OBuFZLDua zT}`Q|hK3`I5q72N*Ncp|R`fnU)qC5HNo9WrE_4SMa*_8f@G^?&rHZMTlFox?6uTI;oe<8m;Lv#|waYj8Fq$H8G*rc84~dw@XFX0Y zp|?xM=9*epI4TrMghDGxceQKHMX0kK9ck4Y8IDE*$h0u;kgBFAfs+}yI*Qe_y6#uG zQq{bQcNu#>FjXO%?%Bj)8FZ7}I(~n31Pw|l=>R-|>)0E`q5yeJSVwV>{vKAh9Ng@> z%rX%qzTOzx_lJ(Z=UPk2d6YKo%X{=QB~2>uAFty+@}A2CEtd!GuBq`&V9jM> zbxwCaRp{Mw=W7j!IeK?SitEh7f>xnWBLNd5Pc)2_t++HTaeu^n+d;@H73eM*tH%hk zEyGEg^eKJSYr_}4255~NS>=BhX?uj7nvD0&HKp)0TT2Ul%b3}BkZbpT_5tH=0ChoJ zirtnRY$(pPiE(kq34E(g0 zW)+-D;%Wm|^j>k&=cd`)Ra{c~t1+?_Kzd4`VT~iF9dYTy*_@8v3iIwxIH1C#-IDIj zCjvakx`|U_{cKE6LpJL4u6oK*Jgdic$C(Htva_CJ4pnfXfp-((71|6*h< zx04o{S&b%0CC55$#dMkq?na8lb74DTr#VYh*zK-nZ z$MesZ=hw}OW2Ba3Y{}^;rLZgs4#>`kfHPr7>Q=R8rM-WXMYKylG!)Qx^Wvx>VJae3 zm>7XQavCsG!?k@EJ5coxSrZ}-jh6EpS>B`gB0>6|L3po%8IKr?KPh)P6$}^%Py^`M z2`w>2yG=`2I;3&?UbZvj6Tp2T_z`dSEm>gJ#u%!(Lug@xzlu8)o_rEr#s60CRzhNz z@buf2ZAX99ZPk4v@)2$&%bDgs)9J;IFD!>z;`Y6<3+x?V&XKoUjh(!Oo7HTeA!foG zCNCV*%BvB9mKG|SJGXP)8$fv&sO~);31Vkvi3+UxSTdjTq;8gk z23(@)dXs=!;B)wPrCdcQ7GY~OtUN45I*@#oFrS>;RItwGgYUm;31#L*kr;KR%Q|6Z z2bzDFu5Y-%J1PoezqY%2Qi>aF=I{R|UoapP)O3ux(v=MX;E@~= zo>_LyZHe4bk=bp~%Mu=`emPGtU^RG*6B9o!^5GIK&dZ#WVkgE!XpvNu91fOb!z+5R ze!w4xJOZt-!Y4Av62pvZ*#(V(Ec66a6XMJ-o2SA)t8lN7609WdL^hX*#Sew1 z_?N_~`_gC(`uvga3lZ^2x39p15*BHEH}!=k3Jg*vMJk7=?;Np;m-L=EaYrZE?0bK@ zo?Tn(mRVm~GlP)6f^IRmQqOXEOr~iOArRtp0-}aVN9`)8qLt*q$@oD$9|Q;?>>0*| zLMUDIHPWxyE9I|ySjLw98z-9WWtdKRE40vmt!e=WS;nTA3E{DvWKXF z>J8SSBnv(tHGlMT+-|R&B(H6v63++k|1QM0vY$GOl`tDzgB?zt3~2~l;nIIlt>Tb4 zX&BttM>W3V(@igL{)Kz!`u;$@!Eu?y*^sVEaLV|&7^gcaj~z5`w8VqAD(I72{Tjk| zo85n3MZdH5=Sp%gz#TB}cKO$7LUS29rTYVbcxt;A5Eqq30d6$$4Um%%E~P?U4TQ$z zW7@g98;behyqzy31idpubbRuP&N z)80f<=lA^>b)Ee}mk&8O`5YL(BuV7ArE-Ki@dk#Pd2bxiPsh{m8!ms4!mR5G?~i;8 zOCTmhYH-9^Hv(fg#IrZ$Q5*yZwy){PwZkD1t&d}W%|WXs%=CYbYUaY`*CD3hS(&R5 z{Qj&eQJZ-R>rbW)|4k(+>r>)17(4vkRytr>MLP;0O<~?jym~zWF}m%ApGhlGb^KA`i=q6Gy;; zlx>DvaY_zF!5GQ69K+)`H+zTF!y8TUh%QNfWSSdn9;F(RIQVy8V$zXz%Qen+{!PX- zEKu}MtO*b6(T*pY8;ec@^&1^=!rp-WU-^%$!>|v>?|h&}8*hIg*Uz8_$c8@~Q;uqY zniFF}fo}1(yRyZpJJFU=hk{G8h4JvR(>!(zxok*cjucl3$}ojpoQ95Yn(envd+`85 zcSMZPV+gXATQz2j&1p=9mC*BeBalm}Zhvrh$S@Hc3SUrf9XRuq>ta256xZjsV_>9o zU1NI980u2Qy##+GH6mOi@ucABgs9)~0cIddC5c^cIu(BUH*Lqp`-nPn7B4_4pt(W?`A`1oreO(DB=`N} z#3%U2eiuM=QbfqXZQ_+sfZu55{ITS&RoCTE`W3|bhI4;zO=6PO_L?#GVR1klWk#N{ z#F>TyU^vMrD*S*-6c&PQ#q}GWC(bGq;PtwFOdi3fja2hCr3uiA7HxS)e?(CXDE1rt z7cAhLF1UBQ#jHP`yy9d<(q`&?T)c*~T5KM^1rZiqG1p;_>U4#&wly4CQQ~|h>bEJ! zZk{ADCPsfgaa(#5p+G5}h9AHOX1@iEwOn>+9Z8$YW{1U|uG;vtXjZ7?Ye%oFx}*rR zkWXA1el&jsf|cURYKTe+zosqL#;ixrj7Sl5Ob&VHdj)^PO~wAo;81EO)xrIa%@EUs zG|C1d+}G6*;n=j7h8I&~30adrt5RtSCQMf0U8;X*dJUv6h06C6mse|A=xtX9M0%qO zyc@3i*c!^*bFPQ77^?ISje8w4P~Ok*Gf4ax_b__YvZ`mFGjm<}GehTQAo<`&XGOz1 zkw&p!(t-Ht4W+4*METAT`qfqd)b~(H;~ZwziI(wyon}m@;?c~-bk`w!K#{t?AG%1Z z;$MH%KJPZgVr?jiebL+ARC2uwJ)2d|Yd^xG8I`Ficcs<$HXG(iY9%ju8@UO{zxg77 zDhQMXEQ{*k&Gg$JS{A+X;>j_g;5XmrWu6um?bs2Ef#3~d7H|bNu9J(&3+7N(Jw#`L z2A=xNPBh{+W1|?TX>GkSjpG8sHtbj^@d1BX=qJxh29nTahTleb6EzOvImFn)=WjAa z3ENevpIAECD@4iFM;uU$ROtR!ueT`H31JE-zLlBGMR)}Xq+u29aRl&o2o1EoQd`65x# z{l=GSF@*|&27W(&exQH{M3KY9NzWM07qzvMR>h<67?ZQMM#=J{pdA(c!w_k zUo}`Y!}ma^Y%ljui#+kP9Z(tilhRX)`+=_F2+rEFb7>A(k z_MwVCz1yLcEq=z8>>qtWmb9i^4DCGQl&r{=#SD@S9t1t>H@#_4l^?bh2928Jl=+O?(b&Yne_FrFeNnk8sg_h&+UKki8WsOiP>P^8_y61JfOI5i#imO+F} z#4U(ZA9Pd$+mGGSdIFN-Yo#G)BQ%N4yUbSOd$!je(AAtsY|VxcnCM*btKxHsfGdA& zvpSl%mCY*hq>E-&Kmv_7UTuH;kpxN&fEC%80H4KZoe z6{oS!9Sq|SxK3{++HehS)-;WR>#uO;mS!E?HSS}w^cV2;*!o7s-NZjRV81idHGX1P zCC1mY6^*aLT#KFQet=;PWe-NOYx+QxX@VtaS6;F~E>xC94524Dt%ZNhWnO4}5cOQL zo_D*5U6vb2yEyp;=bK9+1q7Vz2xECTumByJfI;NP4Lzq+bs4^L+Of1xasnwV;ZILq z^NmyMGznvwe&Cj|`_{U>+pE?V3kI39w&Z(dhl|rBhe6JQ4Q|>m#+CHl zH@;0q0wSZgNYi?-C(?frVu28Yu99ri6HUX@Q_2doDH)1#Cww2x5^VE`Y7|owxk9+z zcG6;_y)6rwoQ9$ynUo7N(Je0mG9EP=11EB7OfB5POC+AE0sUx{TW*atATh5S2;=PY zCVA%IgK5lhU3O)Wo#g+?sm&oUZ_-y%%RlFP`ds=Y)`5j*poD)Mlapwnz8ddoLal#U zk^x%e7Xg;*V#W#OKgeR{mS?DHI^8PlT?z+6ElP$lVDeOi!dTB4zk z;3~HZv26dtK=Xfs;gTUr-yg_-f+Z?hm$4LF<4VaIQ;S(vcRGuKw-fVDf={`H=`Te- z-d4KpC4=kHuttTm#$uW-d#XL**ndsVrosH^`7>Jslq_)ey{kXuZ|J5-MW&ejb0M6AF3T9^{1m_OBTrARL@M zPv-a4)^jd#ie_+v`R}t&23$epiWI5B#HUpdr$39)hvu7v%HX~rWnj%K#RJy-$mWT) zs#FsIXB;5%l}U%)6fmX9moF21WLvzx)(2T(#mB=db7Mt+n4VUihnLr1CBT5KBX9IB zQ%&&=8c1{#jfDe9}J}Bea-|}VQ;greFh$LQ+ zH^9srmpDn(ExF;?L6e^RP$p&JqpsexqinFrWY>QZ6rjCz6F<;lb$1A{XEyJI{UnEu zsb3^&Xf-AgcKN;PTyXqjsJ%HQB_y4s=g*5^hmP)yekFzTZ?lXtPJ*>PM-eGGCnqO!>@fq zXDol(A6}J?qbLn$SYBCrAKOtpeVt7P1T|ON26J1yrpa(hV@Ab4Lzg#q^kh^Y?~cSJ z2(fm87aH@`!sHyXs=|q%JLz$Bv4mh(P?95Gi2+ISxs4DP?7q>=|9NH@iISOpmyZ?I zgUta!7Bw~YO);u|r(v5>n{tADI#uSE1k-=+2sg@IRQz$G6{((Qb8sYf;V^)Y3z`GY+Vd zR@2aIuyrK_vv_EXkSd5n6GK^1?o5AlmydD1cBA2Gi(&*MBo#__sYLQ;R`oF5x%aCM zbCB)sfb$^IAQ~KBmUSSeSVJa;^qA2Y_QC&D2`A&`?%DZwdkagm=&0+%CJ?6h`+Gji zV9EvrFfq9ni_P7y8XnM7cZi#T*)k#p&z;KR9U=o&pmxZ|Rh62ftPOwrN^q!* zK3BmZGN!Y*i^jl^_-NPXX ziBL1m(L--U9Cz}B)E~4{=rN!{U@u)3gU&H5`bsS6*3q5^#Z5@943+z%_cNf-mn6AV zShzO8oFbc%byCm=AkFD0yI#o;JBIj*AvrkhJ zi;f#}GH*4aKWvW33dBzOyLoL2`+#pSL2?fq4+vbAgGQPEj7TApCN2aeowLyuP(@7o zR#I63HduNrWFU$~Hz@me*7VPIMc{wE{RO=DR;Sf9_->@06Nf6HUO#_5gmD)j{Wadw zM7|SxaxM1vz>P_@ZXGUNiw4q@{;!#gzg~_KE1U2e?VCjt;TUpMnstLm@&p9KrmTu)zP>~nb+2h>L4?lN}PiNGi8mkN{)Udi?3YGG2Se0qj+U4d5BttIh4WYhI5~=U$ z-C1!Cz5LURVo8%nc+~x7MWF$Nn97P?h()*WUXn_XhYIn*v9f={@4iPb5s~P-e~)d& zOBGlb#NPeXcRo_7A~zZvDS_DvPacHP@3TXB31QDb{uAPz2CQrz`b&V;tUz;`1ERu` zV45YE_!6wSym@`28Ga8TEvDtgnYED%*pt2@=acyp&CZ;GNN(b-xgE&OIC?_TBy2$Y z)xY||62S%v&d_dAW3sySmo_m0JWoIh~Wyf*i}jKS||?&fN?q@Ux3+90`?z%OM0u0%V7e-ZMJS z=&1HtP?9d(7-?jxEsdQurnKSD+vda`cy~a@o>}=%!q0z)Y84P7v7BLoF`XsAa}Ynv z=0QP6#0$F1TNfxor|)1~kK0F*KdG#MrjZFuf)?LhP&eS4R+Be9!?-i!Lkh+%3)Xw< z)@(@b4I+tx&f6CO>@qOT7*FaltKmc*2SCN}jrPp0C!3f%$_IVal-ko#1#H6X-A~p& zXHt13JRyH@cEg)NqAnpKWY+4gU*bngU@xQWfWG8=u)=sAAl>m&>9O*P%KjPGEVj!v zSQWDIZD5?nh3{t>=|g!u-vOBmF^=^zR$%)l|B>i6_Pdn%1|%&anokhwa0p#bW<0^5 z3EI4AL)u=mLspiQy5g% zvW4Z{Ix3XNbE9w%K5-}BZ6*oiJ4G%Bch&sV%E}RGT9(=$mK3nSm>@@5pSNAE$q4+L z>>q!TR2M@V>o#l^R!P_D`MS!uA{ zZ7YHdD9Mj?K`4cBN#gPqczcr;B)GO)_}P}L;72|AKFN?(0BO~cIE7=q@+j@I})`e?Tq0G-%@~Q-i(K;@u6p+^P@=iTCIoCPxBed5Bt)rG^ z@p2hwdo^K3g-ym_|-XO7y=fEW!yrOz26D07>lBP#1G&-o2;uCJ3Rl5R&?#vHzq zkR2&49l@-DyheA8AOS2&qeNoC)ilmN09G_t^WgbZaHR4MOa2gA#8i%DuYL&^DbH+kX)C zZ~43po$N4;I!qv{t~e5_39js^c9B!2f9q;DLejbNsTR^Cd8cmlu!%3D9OqpJ987o< zYirIyF&E9RCY6O#N%rgu(*3g_vs{AzpukSsT;!*>EApL{Fk|@Xi~P&YBx!#}a#KTS zt=)$U67qoCN4-ia@gDNIQ+d8@tf#8MV{{K9!k#ZF9a@Z4I6PYu-r|udq@04{m+yH} z;8bDPq981pGGW=CdwHy25{MDV?1hlb&5)V#G6jC0e^ns7(c!p)m9=)>T+Vcm@Nauh zclWWrUe7rSY5tNjBTabR6i9Y*5Y7^anmhRBdC8SlfPFT(u*7N2Adtv z=!;GsZHq1ko5p*wcI;f=n%-KXM46G`Foh40n#gSc7Jdo!=1$b)Rwg1v^wDHow{~Wf zk*KpC3S~tp;`fWSmb`z*O43od#wY@fMlkSaW)-p13wM(#Nw;o7{Laa;iKB>OwC(9| zac@*1bs4E!4FAsuqI~tsq^qMo8F(4ZkEOM!(yQ?K`&Q73wKx|!L^#ykq!&nKf3Xx9 zCBbn6CS~?+8J^FKl0P(hvr5~bvJCVyz45Oq619Ycnod##ye5AZg2%0guABVap%x?y znhYlFK~tmZ+x4o24B+y5=8|0d#-X92);W|T+5soqvZ`9FPpAl%eVn%s?Z zoL_4IDnzQ4rj(wFMo#EO3Uz-kh(<_~Vawen{ep|DTR|Uv-at@0Z>9GL(~h?(L`OI(q8T(i2>LAy ztZ*P3cLRk*^ngKe*TT$e%*ER>V~-_#evf03Zu5vVYH@dEA9FnKY1S zYWvZMaheY+B9%nAP)W$0l>MiO#2EYuvf?MG_Y5uBw2|>Ck3phpO27G>gCCy%ckS3k zuaAfy$`-rVZ_gihY@+Z@(zEi;<(F!l1g+c#80H#2zZ*IwnY#>m7*UpZxeYhxYweX; z@kWy?qF8^{axJk3jkp)iK`32wBJ+t?m>0dpg0EsgmisoNxpa^Tod1%{_fFZigrgZO z>3IOv0HC+^+=FM_U6Im`F@;m4yxclr$0=*`ZC#2KS zCo`?kxv1%F6jtq#IbFEy>sU7wF-liy!LMr>`4MrZ-E2;*!V?cRNBHI$L_8^?p`{OAN|I==C-HUY4t z6EuG~h)_p|XsI;C7Hph>o;36j z_v{7uN=6>7?4Jt~^IU;xVO#B&G3wo5A4!n}iUVxxIyt8EnxN=dZ1D?bJ5z9uwH4sL84CJAL@O=>2D3ZS!BzA4q9q2pdLw{=#pHn9)Uu6kr1W z9z(b3`9t_}I8KDx1E4mQ5_vD+=ET_^K$Bp(`)jf|psy2e|A3A0<1VgZWi zGml>UNOC)HP5`j6U$g#u?NcoX9z}n{KfWx+-ka)zjfJ}zE-3VUi`!uFIweR_`pIi9 zWP2uRIA+ACC{nf$68Y$#7~Jh|U1FJG;`hqUY7Wwx0=J)zw2BO;3(BsVp(Iu5Pwu1# z7d}P3f+GjZd%`3rbFLFe9+gq`505fnAiLC~xi6ay*U}a$kM(YobY!bH0V;n`bFGc} z-|Pq|kA&1p6M{?Pj`S%%kpOMAVw2ocWEU6#ui^>vVmIs{io{6y&ckp@8wd z{q~-O|LaW{nRpu^2H}beu8Ys02zFt5b*;}bgAq#kS9^Kj?Rk?+V?1z|{p$>;0Xvg@ zY~py`kG14{^WCy?ge{2|_a}e%(;Ti$FK+KQQ6@(O67tr}d~DCt$i8rv4tOM~+AGH3P*Y6|TMJPYUD|p!;mwa&b3A{x2T=6dls_k3 zfH=1@D3NxjoVjWYZ215%>E&w<+v?w+r1l369)`_^_C3F^TD!PmU1@{r<&BqdL z_hddj8c^|CL1pS6&-k0`VpsIYbyRt1qg$+7DlM+fS!Z-?EE0dmnJb+s2f}PO;+jY) ze@Q&NG^;Q7OvRJMfrt z6A_+}3MoXDw=#cW)p|E1586v^@O(E*gMQ3e&FfnPeyNcHfpY4R}6WJ4%Oe?3=<1WP4h zIV6wTU-85)_Al3F$)&nG9cklp$<0Pw;vKVl6tJ09Yg~WUf_sknf1EranJ4s~lRnJ% z!(77A9(nLz0!Mx9v-nIT?C?~h+Wm`kPVs-^F(?=cg%eGO(e>8(!RaxDp_bm;jP z0}C;M7|S(p-*$P&vYSmaO}k2V=@b(S(>SsJru$IaQ(jHsJ=LnLs9)y^z?-}NwQq9V zAu+G_3eIIh2#?FLyzXN9Je^AQU;CleQ^98Azx00?mYQV}VJem&Hl4Fg)P)^+2C}8G z15_sFIqeEHTKS)u<%ME{pER$F$jYztQD7k>ed$Hzb98RFmuF!Hl`>2EJ84$VQdBF) z)N7eLsaFNM^Bu%AX1_A)Qmr$+>_tD$njL8dg#Ih_`h)Xfkr_La@D{g+qF#1oXX|uh zC?kJ39YQ^A?XE^2*zwKb0gH^f!sgk*Nq`c}VntCcVgxt}4B@+oikC!an8k}3ASULr z7qbi=IT zIk*SfFc+(4qEAm6mDUk9ujmAGgkh$SRlk1&ZZQC>Ic9j~2rpkh19WP=BIu<)(CZq$THH%jo5GMen3my0TDUh+ z=dyfSvq3A%bzzKozNe}+hUJiVey*UiWih(tYRlHU@PKBR;*ScPrz~&WN@9Al|0{y? z2_t~{V?63Ksl1|q7+2pt&*rMCa;1N42>a;fP`i50(RtmK1bw7Ubd*9nl{zL!HQC0> z0PIxZ$=}ql3OoLp=0iZ?DJ+GZqkG%vj=@Y+Ui*RsO*s*hYSl{R zNZfAiJaamypP54Dd1AcW7ucAhc?+lR=$-j0&xL3T!9D^?UFVgf4&5gus%0 z*i6NOmiw9oVTJ1Vzh)scFimUxu{Gwi!4q?%cM&CXfQrq^IZjRA;sv5+V0Flkj zD8FE}Q}!ioJhWYB!v=P!Zfazj^i(OgElI#yYM6p0>G?cS?O|xXw38GAJ&oR3-<9~l zJiqxt&hOT1iON;t>>JQI^Lc;XBSarbx#Hm0$NwB}itt0{IA0-dlm=!e>^+6jyF#sK z?*&XvCDotawTq5m1cekj&qfl0z4vWFJ+Ns=%w-(uC#}GTfco}u>ys_6GE07y7eJB$ zweH6nqzDlN&)Up#$rH5>SG#mCVAJw4zm-bux>Ba zPJ-%HvK9n3qF3uq$rw;VT4XDeBC=BYL6AWmXcqj#c54t_&cvo}nMPrd&p%7U zf}xclt`0NLEpxTSJZfl_MTorfs#<@_6fy-D&MMAAKZ-S0t?n)^AM<)`B4u@X_|kfE z6Lqka_Ts=kZJP9~DIu-*sZeIMvRnxr%6!G<@A>7e>C>9G{tV6fo(4Cq9G-M>x;toAuA3jHgr`v*&;KkOy z?1bdgPC|d?^gnTu)xXa>$eK8E_y-u&qUR)#8^z8#M#c`fDc1=ECH&c0Xv)fFJ3UmnZg?6`% zvFTq3;Vnj7$w(YGQs{GYQ>_$SuCMawGQ43+1On#@bl2e@wLH0XGRLA&49dy`pAf*ZbpDK z^z-WqHr$-XFfXGRM&P^w-cM$|20z)pQ`j1qFV^D+ia(1WFCg9H$UN5bnE(;`p|W80 zldBU>0BjCNp)h5&$!S4T>0xe+6vv!6^Y|s1u|ll-sQ&4x}Myp1o=i)77?XdK@vD#Yi}b>ioxw31^W3ma{kqbH-lWfCKwvwFQ_(m z$a6#a7gf#67eWtShcz?1w_ME$WGt@|t`sM$yZm23;!9QoBsTOqo_+FPK1=0(W`wX-T8*_v( zt<+~ZQu|o^h6_oI3C=|Z3%fGn4(%H@OYudjM0a`s4!So@c*T1dv42?x-kv1wCS-F6 zw*E~R&dx0LZZ(G92NDI?ir{}3hz{BpXY!mnQ>00|D!6r(SuZ`ijz+aQ5=(lGac0Xh6Xm zQ_72J!QZa3uD+c~je_@G+fej#X5&ca9?n+ImW6JV=CO8{c=6i*t8U zvX*M02Kl?pE+pSj2h>$}7DvIbj`{Hoe?`#&2)UVAk}H;JoF|61 zMl6cO;+ytYO=M5Nv+sW=fr8}5il{O@z|np5;`1~r+0zUIC?8L40`p^>NOX@SULi13 zMMT*@TeAMqt9wZjcNGqffkFfTnioS?C@K?YM_FeBw^O2j8$m)wP&(~iN zHxM!VX*|5S>_nh5L8x;qY4BX2B~Ue(fa$MN-Z{OX_hh2@fg&t5TLe|`XWc*8>t7Sj@!NxO~u z=VcwR2H({a*S~*eO9c%dFq`}}$M@^&;xS~0L1R7_3~(1~6_SDk8pGz)SGx5igH zy;(T+8OGCbaCpB9mn;WO(vbSTQ3|PdW$4KhM?ccMh`)1I-#(2s4vFe3IKnnyPWlU! z5-$1p9t4;RafnJ$-7=sKrXhi~6nTutjYT=R9nj$m^`msj<~+*T=VQRESfjygmU80yWm0QDXw6+ zK%vXbVnkoMWf>UAmm_fx!8V>Q*hKT$lH@A7QjPBhXqh65nWh5Tx&6(G<6!ECiqhiN$miMp{(H|)2OAkA4n!LS{ zJ$8ki^5(b&8?j|glwjy7$1CPz1}AVET+Dli(386*pxD2vzv?uPBodYQ-2 zEKz^WM~4Oj>8?gNoY2o!ujm8d)V2QU)9UR~!y3Iq21i`wc<>HYH(0rA?Kit>q0qU2 z0|W-0*w8hiGD#CRSLD=5GNEU`H*He=$suSBRGN>nxiC?Ok>q^)1>w7%vQ}X>I@koJ zNPJsS)~qVkPp)!D2@ki*114K?NuWqgFWrCX-SS-B?LOdOO94|4_ns`oMSQ)j*AvSm zg|&6k9hS!rGSbMc^zln3Rix&^)zSze6=-KKkT0xL9_N0eS>X`_B@J@b_e>FJ@M`6D$Ru|sVH}rP4rSOR1ClupK#fWa0 zV&7N?q3nW}ghP@EZ_{y9W5Fm#vzCBl)NUlYFe;}?F$VAn`lrgPfw4QiHqLpG>f(?G z^{d`G{A)mP9r`*HnlLMR^C*b8<1!KGf3tt_53u&**0?u0XTNk#u1!aEw3r zih2cC>1q?Bdt)@0qlyhKIOu;Y8f-&qUu8D9639AU1X+|5)Dj=&MY(BFTOUIQzVq8s z2Xje|hE?%_3DXN%m_Uf~L4Nd%yalrn6xhhu3bgsj)EQFq6?jN)vOQRxbeeGehRJU5 z#fMT2thH3M7k3UlKRa|U6-CRx>#!>AbuC_^jfh2&HIf?qySjSb`AvWNUx@u;Q5G? zhGoOCk$VKugJk!ztcHJU1rdssL8hE|Dci3;SD{Jl@6DBkm*uc3^hbQ&2i;Sy$)@NG zb9ePZ$*&X?STP<#8x9t~58bz)hcJW8 z{|S(%leafy2&7hKWx^+8Ao4YL*Z6zRv2BR@Oop*QS!@oyHZ_0$q$SZ=6P9si<^7yH zJ=bSAmD6RjYZt6i@Qa7e=;HXRLfz7^sq=NND68OP<%3#iCKG!Tj`AJ9=U)42jgDE7 zTvtHF1Vk=wczwV!Wxid)&&9|Acq)h0MCb{n$AhLIaM(OY-OU%3M(41@kM#rcF|P5{ zgT~FMHeTa%-4CnRvzW8GTH=0VMM+9W zQ8l!vH<7pCk>*?j!vT;_!j%@&I?Lqu1&nB)b$I%I=l_U8?n{q<7>qJ&X?=dFZ6XPj zLIjvQq=dxal!mWO$X|krR5vAQC8hxY%>e#rzt^m+7kNYnfTi$;3dAKPBv_k)6mqzK zN>iam#%mKI?Qd~(Bn$qyI>EKN$#_QfP;_@J-5`Nek`j>pi4W(KbJzvaHC%8eJJtbi&VK9SS z+ZTeTYOw;DE~fOZz!Ij+OkZ?xdkj_g*gXen>ij(fO`*^CI-bS$wFN?=ynw zIpj_i>*zqFZUOz*WavX88}LMz+m3jiGsFTpZW;%;I8inaZ0cg{x7>&>nc=|eC=c>6q} z*$C%Fck=n>pD^+cZwnk-&Q?-uB1oVwvs!w%W3OI45vL$6Vp)XQ!!_D2gLMd zD;dKAE^7*!Jq?V=z7pGf?4iVZX7Y@Fb^*JK=bego7}aEZ(_iH9USWuJ!<7t+*I7%S znBlU2ueo!CHkSdDvJ<6l=$6&2pW8f|5Q0Y zUOtyK;b=BtWe6))DEGM?XdOAb3X|rc9uw8*0Ypp<e8~Gt@5RCH`+|I^O0iw4NdKV?%K5@|FF5yH;M9i^{}` zDgl8ADOkk7JW6yjE#wc%8GUa}&wlxTLjj5+RXVOlU*>jA@(x;D?E|sEFXrc({~Lyf zsP-Kzl!vqnQVgZz>%G^sfMA3;NnnbdDqY53fxI&%AMeW-qh2K}5`9*4s#aT)i^vfS z4o43l|5Sk&VdiH)iRyEuX4(NayX*fU9}3x>WaZqi-!Xy9DPo*V_GP-%vS8VNA{ubj z^6)VkPe6gk_a{84^a+mL*!5pH7=a*ZJuk3%M`ClVa%kywhx69>2p!b(Bx8@%odqi- z(L8kO7!ib%1UJEM{B9=E|_ylS-cZScKr~pUrL_z5=Urx^LP$6433qTmrJ*c zg%$3e=RTSH!QAM-HK5O?xwyE08)_XmCPtj07yi$tIRIW7{H6XH6MU@0TAv7}tCSCy zHGYct^;Hx*XY51k*rDh~bK;pSRBsS+RyYq5z{b3BY6RWV1GTW_%}`RXB9hvn9Th}) zCv_m*7U?MR+b`w?b@tl$Gv6IOlpLf&=>v{j{$l*#!X;XIP9q)4a;CC>g&i{}hf8j9 zfDDxsHw-@jiZ~*GbP2!e_{X8yZAtkm{l2xRlT_J|xho$*Fb5hRMUq zFxm@RvGCAjWe|SJKVnyG>`G9<-63tZER(F zE$mJ4gFHmmav)|m&yTk=_ zz9b9VNBM2hvb0WxMNU|DOh88R2%Uwp3$iJ6d$CKqMcZB!4p@SJu!_cuD3Ub+#c>ze z8Cz4aPAuoZ9)ME%EnD?#*7=c*O;{~qED0fMf4R~?0b7rFbWh$+Q3N|A^~~|3vQ6%8 z1lh7sT^nP>i$PJ~JcGO-^RM45;Bt`VF_Gdx!VN<=kfu*$;|eG4i^<-s_kPTW;Jh(Y zx*8?#%{x`8nUAS|3<33$LYmz!e%x@Xn^NFlN?&v^hRYNKJehoQKan)KeiIb?u7F)V z0!e>3R!)&nutr)D{`lU>hXZu<-fq*0SDEThzNqrz_s<*gKg)-X^_!)`t*Wg zh~n+ZVm`pth)@Qde;iUt!9-*50k7jkf(qwN3#kGk%>1c;E|=-2^J=3bJ0E|%67M?t zfDNV1D41`*@CmKbma?Ci*=eD`s8}R!l=~A zE{ew|8rFm6*}XdmL;MQl-D1|Is03&0G7?E+g7RuW0We^Q2}Q`tc6Rw{PyqVi!s*r) z6{&ANq(S6=nN2&U$`4J3I+j%9U&3l&hS4)Dh_^Yx(5$AlnKM0I*8DoVv5XBh@<0}? z7@(uPQNnRP6iz~a70|Q?$+xiI+lx4=zA6>|qGr8D8?=c~YdF!3EhgqbKcNf}gXnNE zs2;<#fz_!W384&8E`-E*LB1ggmgdYC*Q^>hw>8gy{y3;-ums24+=QRSgj`|)R2Z?g z=0DrlwdshETY#*zDS3Ba#dlqJdETW1Ha^{1^jtuXV={m46{4)TST_)WmW_BL+0>JC zh?6@$_Zn9xuPi{WD%QB3ThK+XAV|O^F*TrY6ciAjuOUk2xUr`s*FhQ0ELL#+?Sx9A)#%;M9G@52U{w!1@?SwZPvaHU3@V95Xftjh78Tp=#Fwqhy)Sw`_tqO4! zJU{*j7TQDL3AENS8PS$x+`+1WtkN5Qf2n){ZCw?>kLH1K zl{vT^sJ5^~sMobSZd5AM6ZH_D56PID%6q4+=d@tE=ovb~lkU(zSCxUk>SpCI=fQ|o z&?z$U+^J9&fkShBD&mMGQU~KV#PnZ(MIGDl|1S3zq5ziF?GGnc=!>ChWt^2J84Ff1 zaq9MVLlwU4-Z*>P=%710`XKJ()I|OL8SkRoD*+r_io^qR31))xXJ9_Ezoe!u8~H$P zgQj|+L$R&IUQnuvrlz`|rF^k8;fM6FUKU^hWmW6tPL*7CvVdNz!Re}Xbv)mHAeFQ- z#gCgr9WL_&nW)X^&JsHX)%mpXYslAbYR5{#vI!*wlU937RLV<02UD1Ie1>CIlf_Rk z#<&rsM^ST~t=Ye(mDouk<{(|e7Nl0pbe+mwHDF4M^;)ZErgG1$l;g=TjEDIr=?)JV z(=g>Y2%ZBgU;d?2j9eZ}*Ny#uK8v}srJHUG{&f7zPz;*Xk)@2+8vF1ZMB$)WOFked z-M{CoO!`jmF!j=|@3c)_&D8RhfBA$;TEEg5{xmdoSvH{Ev5bBH^T~_coE=>N+C(-L zVnUw1uny7$_uj|r6wcTP=%WhjCnu79WWQPc8!7S>RI1tHsEk6RVQ ze}yFm9E7DTVl>QS*(q$TEX?(g6AwgXm*wrFp>7kP`Avtrxx6nAktnLab}BbjCBvlZ^gv9V7fGf!x;ibecEFYwov2M|y5BL{ck$C%h@mPGDxPNp=qMfe zOoeN9!~+>&PhbJMGczuKJS(uHKs0QGsR5uf*)3M#pTi;+D*jMgCQ%H;@qX+19q>)V zK=LUm@P0lp^10C+ww{JUXU*^dlGXfbbu!<-)fM3SaS8GlG(UXXJh>&>$e zq(}XKjc`5Diz~qK)gj$^ITX3hKv{1tsU56yNjX@A6)|2Tx4wLTfp-{E)#-yQn&Jy+ z9^_5^iDLRV%aV}yD_v!s#M1Y?4ja}jM-+3a3L@D+UH6hT`r%2}cv;UuKz@s;Cu|!b zS-{hO|_ZxMhEiwwZB;j_nUF8nveos(ugb98)Hu4s=$UZPufn zs7Bso_+KwhrgrUH%>1)xwq9B3!`CNCPh#}RJ=z28l3H_rHX)~F4NN|C=~)%b83_N4 zkrL%QK`4NknDiqpd8D^~WGpIJ4#&(au3%jF;#nlNk!H{Kxg;^S$$M)U25+<{qH7Co z9tL9GY9_(EF*RbFB}hk4*^}k#1R*Fkl6e>A`cQ@q-{B_I!p2CM81EKV1Di$Hnr1&4 zUp*4EkF&*pEY8ZcHM@cuf=XvYV#@)cYNsDap`qc}@;~O$QOT?zujAqGD(LgfS5ov@ zop*+;sQyk4aJ)wo zdexyDC7OrU(3j;XV&^y?7|K7SU?LGGNKBoo=mXOkOP~Ghyk4nrcExDb{q+~Qly*^z zG6BBS6PB(2(rnf9;?t!<7(A9aGEz}(JWWG?0xFa^%{IMqu3xws;Sbvh$h9#u6Zmy9I@|q*>D>^MNzY#f zJG{$N4^#vhgnaV8mIGB1RMpVA|8+cn9Q_^JX8zF^O7mY&vvSsa06LdS>GPn=uzvP{ zvBIv`l?8p2c3MNQ_oK|wG;c?)XiWmM>@(vb_6-fIyuns$qQ9|{?q7C%krl1dypP_W zwh#?w7we-->_a;ua;$k2`A346(d*qb9Z5fZHuAZ+4rA<>b{HJ|bD%b2nYVv`>xHr9 zBi+=2Fcyx-29+tSs#Ga|^P(i@S-LemD@4y)ibqdD!&VAPob9@1O5M`D&8zz6{d%bv2RRmdg!6EAtTwU3+~5wqr)LmI0m%v} zv18KFiEx1AMB_tjO+o#L`;ypy@9E#%Ec!DvGb||vD|H+}D3PNJ_dpVCS)`}c)HFf5 zH*Ul1dVxB0<6w`-qr;|iQ-Q)7^P7h`o0xwGTGdrp>H!~pw|W0p)Be559+Vxcb)Ym~ z63^1gWf9?$fG2IeCm$Gh{ChFA-eN*p$p3@kDB`+aK8)fiUwtwzjrI_K>@46Oyc63s z(7@B8g)$CxW~eIPg9ds#FI!UZ#oG+D>{l=ddG$G-Q=N%-*GS=7EbMp{Y%IPj{x>u> z(QLW``80UAYsG!R!35dq(>K3=(~6}O;NITA#YYy>{SMB5OR4UQTad)Sc`)S-w)BBnc*1{i z;P*`?oiLj(rRn$B9(_Ng%Zc@UOY?JpAFh{SA_P_GCX8W53)xp6ugx}OETyJ)h4zF? zxGN6!U{^y*w2pq=E!Uq5x*0c0ewCq{_VV{F$%{x(&@^~xgBDPK!3t^``idyRJ$8l@ zEgV3m!#=MaL!pSdMKPfiC+fja|SFjk^VZx=U zq+wEh`~)&5{G!PbY)%#PQHRZx{37r3qDINjx5q?!B0>7g63FaN#7OuW!HPxiQO*l# z55a5XFW$N0)s6jsQf1NCv8cs!vy5b&cjv%3yTK@KmajQ!oQxBHfgw7)q`c!tqB;B{ zhR4w>Y&Ymx4k^SJZug%1yZt&7V^*tWMx^G@>3`%;ib-_=4}64_oF{y!ulCG)ce@&5 zPj-7%*?oGxf~!^6N0BLj;F;!1nzjfKyX|C+;&)<`{aJQ@83@U!sz7CpiW=3!XN=27 z;KjDvrAssTC)|?}K-4#wFHWxW*50Lx_l35)i%3!Iu2}lv*7S6?2&e3J&b61flBNoK zBkKZas;+~TsQTV3>ZGmKBfc%#!<956?!sWRk#rxG29Q!i`-LtY zb@+LhTILf8pvp~5QnMhM1#KM2J?b7feD4IM39U!_JiQ-`&kmd6WEJ=9fh!C8ZzvRm zs<}$sN0F;nMp}CZrWtO{aZx`5I8lj8oJ?e=>N>T5N_Xu)yB~HhMz)@ScNMHsAC}*mR0`aJ`)Rcnx&v;U3@Vwyb1o@u!Y}Loafd8N)Juk%T|Ht(b8BD%zf9{dh6lx;LZ`lGNRJRA76&XkmbO;9yl zOYIBI!$aOT0-{3VbkK-y26!+k=*g8#EDUQiij1AvD1n7KxuN=q`bOyxM{w zOOHRs#!!zA?e!HIv}AHpO5_){wm()6Z&S%r$qQcpi9*Th7T5#(oMEC!PE&Y?D@C{* zrRyo=`=%fJB!KYyo^hf%OD+&CNF^wf#fHwY8T%ZW>y-_r-;mRDpSp!y2bSNQ&J(=K z%q)8*+%a9g8b=-sjx@R*X&G662>EF=&g=R<2Ndy2)Psgh55~vvSah$a_El9>xybue zQs)N_1y67nP?H9=L06jTl7M^-7JcfxiTUAdX zwobz;1MT~y=N?SYroO=l45vIhH@8~PMMg69oz-QFv<>CngDICG<2q`8`;naXc`-Vb z#=~&vz)>G^^y}4xrb@!MRK()bUnIT_yDr^*^wQH7CO*obll7QmPD}ON#=m+iZuY9$ zGlFgFb4Gc+DB)u0zwz(kQ3Brh<&z#G7^3Qm^U!)4ftg>YD6zS z-2a&HPOwwp>pVsBWa91HMNQu;(`>OcP43zaXzK%U0}!1v```FXixXByb^QccGRxXW zSq_FJmies5B159z^~pv*%*3f9u7}QSrG-UC?k%IQ)~qCZDB4GVJ#tfFSYNnXbEqmt zW|-viyBr@-^tE^W7Hc&w!!)M79*Ro$mT?d((NF?1x2!(3foPVKoL*9OT!9z)3h+%! zC6zA9(V;{HkNLX&cAY#H0|7LZo(x?4!mIIDKr=BRM%4L39Ye`~qrAi@B$pJ)r`B+~prtrBgP|`&@;a9@Ib#4F9x$DOVWq#tCf_&dTeozHh<2 zgk?6YXV8PkT-y3I#xhU}ukOa|s+yBMz0B6**CRH_f~w|r|oW7v4TDY_MpexNqa)R$Xm4V)^}}w;j%F zUG#0(I}>n!%SqFXUB(VihTX9;kw`4-%tN+adziyA0`NtKD_(|YXKLQg-5?pW7t{B^ z80o@OVN87A*1DF#GiD>0bXBH|d;SN7172H0tFlBh7q@pc7_m`CZmcl9Pe= zL)vOw195G}&fszLy-rA|(ddWbBn5dBrzY+k!1DL1I%vKlA zlWX1%{;WzPW9H?mG4Z!U-(-}XE(}|_pJtbwShh7{a3<_I3nWVpv;caANmoBJH~^4V z*seH#KN8?+Tj-> z{zHMnKMfmB=xHp`B%i>tn%%80gX0@QYn`pdFyV{H#r^^~D#5jXR41`K-ppmaE6$k# z*uTgT-3*J7d8W}LHwX9bO7$~aW^;ga{(dR5|3?I;5N&|RR5&knV9`ToP3j%o64){*_13bjRl zGLb#p8Y)pLiwUcojn0iemx-1?b*yP2}Q=|6V?` zz_WO0b#MpnHP<*Y6MBDMlMmDe)uwM0zK?kip3Kj}kzNC`+w=y=LO}}2rX$#<5-ABVtD85D=1Ts);~~6I!Y9` zAp#at>~V|f&XwOHkV{BsZml1G6qVptr2=|UD;yM`TVUFM$trj+Lptb@46o4=uSAXg zy*|XIT>mT!vkWxjb}oN7XLGKu|HUca7~G5;^JcQ%LLGNjs!%%S8x+}Aq=$}C?XIQ; zKCrDnbaGzuZOw(ahw6;jEn(>KgBG}!ifmQ4AKhO`=at~LSKoIB1-}n}CS<`t)eYhr zO+JA#xvLJ;EXlqNla3vb#j~TE=w?@CwAuOw`qji zDUW-g(vQKV?d$vP-<6SEpkNV7h$@mJLu}*j6&D1{#3$pfN60Mj9Gq=yybT z;~)TL*au>tb_S=Yn)(`l)Y5(;5%oL-#ds8zq(ENkm&WijG^jQ50t$Jfn71Ur_b;@$ zb}m=Gz5HK;dEXj5QR+{QQ9l=HXJ`` zGJkM8MVOa~FhfPkb%KbOXW`==q<>jhf;jMf)X2*hO>44k@4!rd7b>KL?!CRNtfkh` z4DRAG`yfO{0)R@u@{#LOoCje}oicJ}QFHQX6&E?NY}n{2JJk^Qr&d1Zd8UK9LJBmw zr=C_*?a0GH!=3TX-bykR;wUKJDU+<|*r6#amsw4TKQ|Iz82^In;nrE2!=i0)x|-tz zDu&eGqj*L$uai`)9aU$9O|+cBO4d|Q8r3J`69)N-?y{eZCAiF6y4aIcR2kFM z@Nw}_Rko?#&USlkat?a+HiG%1C{fzN6&yiuf*ffoWwac}>tN*1T&EQ!==)8WYdGzp zG=jYu(bW)`ql01&_jcUWxtqHPA~jy;TbRr%m78*c z)DkB*w~LWJGN}MIE`zNWFOtj@H6Qbq8B`tZ2(ADMOHslmHzyWL^F__>&I8d-*ppsY zodX~{l$6|m@|=T2Jq+I*7hsfn+R_+^I1&9A!?1?NXTo_DbNliXnQna~b&^TB$iI{k zuBkMcR>e37P^hnotE2Sak~yEb{zwZ2wz+ZbWlLqf+4Xv3e~|p$0YV#E;WJ+ci>aEw zgH6hw?~}0}2)q|2bjp|C#~qnYehC>#zyi@@^ORD5wJ0O`LA1C-7lrg#i^$PL(Tzp$H`AYy(r zXX{?W3mC-pRd7-{nX)sn^?F-IM+`ltj8UlS+*UG$PCPix2&d*R21z>D$hI=TTi)Cv z6R3HAXB)B)(83Hd1*a*ESe1N!kt_xq)wPoad{*<|QE*}xV%nbVA3**S>p&Lg#F&VC zxdH(??=EA*4-Gl;JF;>~u5;9N zHWHY}rs8&KdW5YKf7LEL$*hPu)rIRhjnpFEpEFv~P=AdAVj)fVSk4+SYk051f+xU9 zx4U*uSxR5`od*eb!70Y6{W;!i=Fz=0IYjqyOJ}bKnit65Fb%6+Uvq#52$HCZz92^m z&vQ$xf(=dDv>1v^noLyTOC*-BxMIsc5s{iC$GT&-loC9~5V; zz>FLsrRIOj_d^F7#LWC*efm6qqtALgWxTBEx#L;2I+A5h6G!Z>k{lc6!_ACC zO3yUxU`Aj^a5X26#8<s+ zg43t&S%&FmQdqv}cC1^`N!I={QFWXbj==HAMe?a3&q>zTIhRE=ece-=RB}L zdV|)V`rXee5-(W_8uP6)+Fqe&%p!U_7$_`BU)>L)POGlB7AuwcNBU0ZQsR;ZRvDf|p5oHvMDLPITZ3>nwBf@K*b==Q+twS4^yvxW z_X!a^iu%-;Qun#X^HrBk(U#vUEUBr^b4(?Le1k$U$4sA=XkpKP4!Tnx3Ovkm*mSK5 zMM#rvvW)5Qb{3o&C zGt=mmedExx+RlBZB!r(!dK*S@8cm3h1`se?Hm)O23#td<<3}Ku4E`xONYmlo>)qBN z&DOv7YwXyTxExF~b=yw(qIq88?2N z)N!Y&gX2Kuz8M>eiFJo(K?z1n7go`>(5;w7mB-o#D;F?Wj3;fmU=KqMmeg# zcF&W^QQ={KRlSPB-`?Zt0S8NjruY_7S3#-kSisa<$J)d)=jwKQ99`?U)?t}@cAq4~ z1=MAtFH0AV<~P9b5Z{|T_gEraL$+Qtn_F%wi5ck@$Ym}BvG}0o;T52xDp0`HE}lC$^#F`unEdaIYbN%JULu+ z)JlP7&`L@GVsxj+1^lHa@dat#?jkS1cnwKxFq6FfR{Zl$iPo3#B)zrojuPD-Y-S>r z{bjauumh(RxnktrsbAw;e-WiTsSpwJQC`rz5-;Tj00000?XqF| f00FuG58%?20RR91JzBe%o8wVH0{{R300CKA0%F_e delta 252015 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%0D-i1=W}!i_zTx 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