From e55cbff94b8ade327a7debbb899cdf2fc784f5a2 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Sun, 5 May 2024 14:26:07 +0100 Subject: [PATCH 01/15] Initial implementation for reading packedancestry files --- DESCRIPTION | 3 +++ R/gen_tibble_packedancestry.R | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 R/gen_tibble_packedancestry.R diff --git a/DESCRIPTION b/DESCRIPTION index dec7b480..ea2ac191 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -37,6 +37,7 @@ Imports: vctrs Suggests: adegenet, + admixtools, broom, hierfstat, knitr, @@ -46,6 +47,8 @@ Suggests: readr, testthat (>= 3.0.0), vcfR +Remotes: + uqrmaie1/admixtools VignetteBuilder: knitr Config/testthat/edition: 3 LinkingTo: diff --git a/R/gen_tibble_packedancestry.R b/R/gen_tibble_packedancestry.R new file mode 100644 index 00000000..de531133 --- /dev/null +++ b/R/gen_tibble_packedancestry.R @@ -0,0 +1,33 @@ +#A function to read geno packedancestry files +gen_tibble_geno <- function(x, ..., + valid_alleles = c("A", "T", "C", "G"), + missing_alleles = c("0","."), + backingfile = NULL, quiet = FALSE) { + # Substitute .ped with .map + map_file <- sub("\\.geno$", ".snp", x) + if (!file.exists(map_file)){ + stop("map file ",map_file," does not exist") + } + ind_file <- sub("\\.geno$", ".ind", x) + if (!file.exists(ind_file)){ + stop("ind file ",ind_file," does not exist") + } + + res <- admixtools::read_packedancestrymap(sub("\\.geno$", "", x), + transpose = TRUE) + names(res$ind)<-c("id","sex","population") + #TODO check that allele_ref and allele_alt are not swapped + names(res$snp)<-c("name", "chromosome",'genetic_dist','position', 'allele_ref','allele_alt') + + + # using the gen_tibble.matrix method + new_gen_tbl <- gen_tibble(x = res$geno, + indiv_meta = res$ind, + loci = res$snp, + backingfile = backingfile, quiet=quiet) + check_allele_alphabet (new_gen_tbl, valid_alleles = valid_alleles, + missing_alleles = missing_alleles) + show_loci(new_gen_tbl) <- harmonise_missing_values(show_loci(new_gen_tbl), missing_alleles = missing_alleles) + return(new_gen_tbl) + +} From 087fee3f5deb4c4639b4b22df1d046edf1a5d08a Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Mon, 6 May 2024 10:00:19 +0100 Subject: [PATCH 02/15] create af tables and precompute f2 --- R/gen_tibble_packedancestry.R | 5 ++- R/gt_to_aftable.R | 83 +++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 R/gt_to_aftable.R diff --git a/R/gen_tibble_packedancestry.R b/R/gen_tibble_packedancestry.R index de531133..9915cf2a 100644 --- a/R/gen_tibble_packedancestry.R +++ b/R/gen_tibble_packedancestry.R @@ -6,7 +6,7 @@ gen_tibble_geno <- function(x, ..., # Substitute .ped with .map map_file <- sub("\\.geno$", ".snp", x) if (!file.exists(map_file)){ - stop("map file ",map_file," does not exist") + stop("snp file ",map_file," does not exist") } ind_file <- sub("\\.geno$", ".ind", x) if (!file.exists(ind_file)){ @@ -14,7 +14,8 @@ gen_tibble_geno <- function(x, ..., } res <- admixtools::read_packedancestrymap(sub("\\.geno$", "", x), - transpose = TRUE) + transpose = TRUE, + ...) names(res$ind)<-c("id","sex","population") #TODO check that allele_ref and allele_alt are not swapped names(res$snp)<-c("name", "chromosome",'genetic_dist','position', 'allele_ref','allele_alt') diff --git a/R/gt_to_aftable.R b/R/gt_to_aftable.R new file mode 100644 index 00000000..dfc96231 --- /dev/null +++ b/R/gt_to_aftable.R @@ -0,0 +1,83 @@ +# admixtools equivalent functions + +gt_to_aftable <- function(.x){ + if (!inherits(.x,"grouped_df")){ + stop (".x should be a grouped df") + } + .group_levels = .x %>% group_keys() %>% pull(1) + # summarise population frequencies + pop_freqs_list <- group_map(.x, .f=~.gt_pop_freqs(.x)) + + afs <- do.call("cbind",lapply(pop_freqs_list,function(x)x[["freq_alt"]])) + dimnames(afs)<-list(loci_names(.x),.group_levels) + counts <- do.call("cbind",lapply(pop_freqs_list,function(x)x[["n"]])) + dimnames(counts)<-list(loci_names(.x),.group_levels) + loci_new_names <- c(SNP = "name", CHR = "chromosome", POS="position",cm="genetic_dist", + A1 = "allele_ref",A2 = "allele_alt") + snp <- show_loci(.x) %>% select(-all_of("big_index")) %>% + rename(dplyr::all_of(loci_new_names)) %>% dplyr::relocate(all_of("cm"),.before="POS") + c("SNP","CHR","cm","POS","A1","A2") + + aftable <- list(afs = afs, + counts = counts, + snpfile = snp) + return(aftable) +} + +gt_extract_afs <- function(.x, outdir = "./afs", cols_per_chunk = 10, blgsize = 0.05, quiet=FALSE){ + if (!inherits(.x,"grouped_df")){ + stop (".x should be a grouped df") + } + if (!dir.exists(outdir)){ + dir.create(outdir) + } + verbose <- !quiet + afdat = gt_to_aftable(.x)#, inds = inds, pops = pops, + # format = format, adjust_pseudohaploid = adjust_pseudohaploid, + # verbose = verbose) +# afdat %<>% discard_from_aftable(maxmiss = maxmiss, minmaf = minmaf, +# maxmaf = maxmaf, minac2 = minac2, outpop = outpop, transitions = transitions, +# transversions = transversions, keepsnps = keepsnps, auto_only = TRUE, +# poly_only = poly_only) + afdat$snpfile <- afdat$snpfile %>% mutate(poly = as.logical(admixtools:::cpp_is_polymorphic(afdat$afs))) +# if (verbose) +# alert_warning(paste0(nrow(afdat$afs), " SNPs remain after filtering. ", +# sum(afdat$snpfile$poly), " are polymorphic.\n")) + admixtools::split_mat(afdat$afs, cols_per_chunk = cols_per_chunk, prefix = paste0(outdir, + "/afs"), verbose = verbose) + admixtools::split_mat(afdat$counts, cols_per_chunk = cols_per_chunk, + prefix = paste0(outdir, "/counts"), verbose = verbose) + block_lengths = admixtools::get_block_lengths(afdat$snpfile %>% filter(poly), + blgsize = blgsize) + block_lengths_a = admixtools::get_block_lengths(afdat$snpfile, blgsize = blgsize) + saveRDS(block_lengths, file = paste0(outdir, "/block_lengths.rds")) + saveRDS(block_lengths_a, file = paste0(outdir, "/block_lengths_a.rds")) + readr::write_tsv(afdat$snpfile, paste0(outdir, "/snpdat.tsv.gz")) + invisible(afdat$snpfile) +} + + +gt_extract_f2 <- function(.x , outdir=NULL, cols_per_chunk = 10, blgsize = 0.05, quiet=FALSE){ + if (!inherits(.x,"grouped_df")){ + stop (".x should be a grouped df") + } + # if no outdir is given, create a subdirectory f2 in the path of the gen_tibble rds + if (is.null(outdir)){ + outdir <- file.path(dirname(.gt_get_bigsnp(.x)$genotypes$rds),"f2") + } + # we will place the af tables into a subdirectory + af_dir <- file.path(outdir,"af_tbls") + if (!dir.exists(af_dir)){ + dir.create(af_dir,recursive = TRUE) + } + gt_extract_afs(test_gt, outdir = af_dir, cols_per_chunk = cols_per_chunk, + blgsize = blgsize, quiet= quiet) + numchunks = length(list.files(af_dir, 'afs.+rds')) + for(i in 1:numchunks) { + for(j in i:numchunks) { + admixtools::afs_to_f2(af_dir, outdir, chunk1 = i, chunk2 = j) + } + } +} + +#gt_extract_f2(test_gt) From 138ff51e973cf463cd5f1a9e5137061b9e17cb53 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Mon, 6 May 2024 21:21:44 +0100 Subject: [PATCH 03/15] docs --- NAMESPACE | 1 + R/gt_to_aftable.R | 20 ++++++++++++++++++++ man/gt_extract_f2.Rd | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 man/gt_extract_f2.Rd diff --git a/NAMESPACE b/NAMESPACE index e1cc431e..a1a221f9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -73,6 +73,7 @@ export(gt_as_plink) export(gt_cluster_pca) export(gt_cluster_pca_best_k) export(gt_dapc) +export(gt_extract_f2) export(gt_get_file_names) export(gt_has_imputed) export(gt_impute_simple) diff --git a/R/gt_to_aftable.R b/R/gt_to_aftable.R index dfc96231..aa49b776 100644 --- a/R/gt_to_aftable.R +++ b/R/gt_to_aftable.R @@ -56,6 +56,26 @@ gt_extract_afs <- function(.x, outdir = "./afs", cols_per_chunk = 10, blgsize = invisible(afdat$snpfile) } +#' Compute adn store blocked f2 statistics for a `gen_tibble` +#' +#' This function prepares data for various `ADMIXTOOLS 2` function, and it is +#' equivalent to `admixtools::extract_f2`. An important difference is that +#' the filtering for snps (e.g. by maf) or populations is not performed by this +#' function; it is expected that the `gen_tibble` has been filtered appropriately. +#' +#' @param .x the `gen_tibble`, appropriately filtered for individuals, populations and snps +#' @param outdir the directory where the f2 stats will be stored. If left NULL, a directory +#' names 'f2' will be created in the same path as the RDS fo the `gen_tibble` +#' @param blgsize SNP block size in Morgan. Default is 0.05 (5 cM). If `blgsize` +#' is 100 or greater, if will be interpreted as base pair distance rather than centimorgan distance. +#' @param cols_per_chunk Number of allele frequency chunks to store on disk. +#' Setting this to a positive integer makes the function slower, +#' but requires less memory. The default value for cols_per_chunk +#' in extract_afs is 10. Lower numbers will lower the memory requirement +#' but increase the time it takes. +#' @param quiet boolean on whether the progess should be silenced +#' @returns SNP metadata (invisibly) +#' @export gt_extract_f2 <- function(.x , outdir=NULL, cols_per_chunk = 10, blgsize = 0.05, quiet=FALSE){ if (!inherits(.x,"grouped_df")){ diff --git a/man/gt_extract_f2.Rd b/man/gt_extract_f2.Rd new file mode 100644 index 00000000..78067dff --- /dev/null +++ b/man/gt_extract_f2.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/gt_to_aftable.R +\name{gt_extract_f2} +\alias{gt_extract_f2} +\title{Compute adn store blocked f2 statistics for a \code{gen_tibble}} +\usage{ +gt_extract_f2( + .x, + outdir = NULL, + cols_per_chunk = 10, + blgsize = 0.05, + quiet = FALSE +) +} +\arguments{ +\item{.x}{the \code{gen_tibble}, appropriately filtered for individuals, populations and snps} + +\item{outdir}{the directory where the f2 stats will be stored. If left NULL, a directory +names 'f2' will be created in the same path as the RDS fo the \code{gen_tibble}} + +\item{cols_per_chunk}{Number of allele frequency chunks to store on disk. +Setting this to a positive integer makes the function slower, +but requires less memory. The default value for cols_per_chunk +in extract_afs is 10. Lower numbers will lower the memory requirement +but increase the time it takes.} + +\item{blgsize}{SNP block size in Morgan. Default is 0.05 (5 cM). If \code{blgsize} +is 100 or greater, if will be interpreted as base pair distance rather than centimorgan distance.} + +\item{quiet}{boolean on whether the progess should be silenced} +} +\value{ +SNP metadata (invisibly) +} +\description{ +This function prepares data for various \verb{ADMIXTOOLS 2} function, and it is +equivalent to \code{admixtools::extract_f2}. An important difference is that +the filtering for snps (e.g. by maf) or populations is not performed by this +function; it is expected that the \code{gen_tibble} has been filtered appropriately. +} From 7c6935cc775f3b7e06aab3ae5c5781bc77fc3db7 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Tue, 7 May 2024 13:00:06 +0100 Subject: [PATCH 04/15] read packed ancestry --- R/gen_tibble.R | 13 +++++++++++-- R/gen_tibble_packedancestry.R | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/R/gen_tibble.R b/R/gen_tibble.R index 38eec8e7..cca1aa85 100644 --- a/R/gen_tibble.R +++ b/R/gen_tibble.R @@ -3,14 +3,17 @@ #' A `gen_tibble` stores genotypes for individuals in a tidy format. DESCRIBE #' here the format #' @param x can be: -#' - a string giving the path to a PLINK BED or PED file. The correspective -#' BIM and FAM fiel for the BED, or MAP for PED are expected to be in the same +#' - a string giving the path to a PLINK BED or PED file. The associated +#' BIM and FAM files for the BED, or MAP for PED are expected to be in the same #' directory and have the same file name. #' - a string giving the path to a RDS file storing a `bigSNP` object from #' the `bigsnpr` package (usually created with [bigsnpr::snp_readBed()]) #' - a string giving the path to a vcf file. Note that we currently read the whole #' vcf in memory with `vcfR`, so only smallish vcf can be imported. Only biallelic #' SNPs will be considered. +#' - a string giving the path to a packedancestry .geno file. The associated +#' .ind and .snp files are expected to be in the same directory and share the +#' same file name prefix. #' - a genotype matrix of dosages (0, 1, 2, NA) giving the dosage of the alternate #' allele. #' @param indiv_meta a list, data.frame or tibble with compulsory columns 'id' @@ -85,6 +88,12 @@ gen_tibble.character <- missing_alleles= missing_alleles, backingfile = backingfile, quiet = quiet) + } else if (tolower(file_ext(x))=="geno"){ + gen_tibble_packedancestry(x = x, ..., + valid_alleles= valid_alleles, + missing_alleles= missing_alleles, + backingfile = backingfile, + quiet = quiet) } else { stop("file_path should be pointing to a either a PLINK .bed file, a bigSNP .rds file or a VCF .vcf or .vcf.gz file") } diff --git a/R/gen_tibble_packedancestry.R b/R/gen_tibble_packedancestry.R index 9915cf2a..6942f294 100644 --- a/R/gen_tibble_packedancestry.R +++ b/R/gen_tibble_packedancestry.R @@ -1,4 +1,4 @@ -#A function to read geno packedancestry files +#A function to read geno packedancestrymap files gen_tibble_geno <- function(x, ..., valid_alleles = c("A", "T", "C", "G"), missing_alleles = c("0","."), From 721174fbfe7367959db9021bd4df7aa7c99f5a79 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Tue, 14 May 2024 16:06:29 +0100 Subject: [PATCH 05/15] test for valid alleles in geno files --- R/gen_tibble_packedancestry.R | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/R/gen_tibble_packedancestry.R b/R/gen_tibble_packedancestry.R index 6942f294..9db41dc3 100644 --- a/R/gen_tibble_packedancestry.R +++ b/R/gen_tibble_packedancestry.R @@ -1,5 +1,5 @@ #A function to read geno packedancestrymap files -gen_tibble_geno <- function(x, ..., +gen_tibble_packedancestry <- function(x, ..., valid_alleles = c("A", "T", "C", "G"), missing_alleles = c("0","."), backingfile = NULL, quiet = FALSE) { @@ -25,10 +25,8 @@ gen_tibble_geno <- function(x, ..., new_gen_tbl <- gen_tibble(x = res$geno, indiv_meta = res$ind, loci = res$snp, - backingfile = backingfile, quiet=quiet) - check_allele_alphabet (new_gen_tbl, valid_alleles = valid_alleles, - missing_alleles = missing_alleles) - show_loci(new_gen_tbl) <- harmonise_missing_values(show_loci(new_gen_tbl), missing_alleles = missing_alleles) + backingfile = backingfile, quiet=quiet, valid_alleles = valid_alleles,missing_alleles = missing_alleles) + return(new_gen_tbl) } From e1efa1bdfb419fe9a7656d99a7f24e7fe1e7cf1c Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Tue, 14 May 2024 16:33:06 +0100 Subject: [PATCH 06/15] minor fixes --- R/gen_tibble_packedancestry.R | 13 +++++++++++-- R/gt_to_aftable.R | 4 +++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/R/gen_tibble_packedancestry.R b/R/gen_tibble_packedancestry.R index 9db41dc3..b3dbd0ef 100644 --- a/R/gen_tibble_packedancestry.R +++ b/R/gen_tibble_packedancestry.R @@ -13,6 +13,13 @@ gen_tibble_packedancestry <- function(x, ..., stop("ind file ",ind_file," does not exist") } + if (is.null(backingfile)){ + backingfile <- sub("\\.geno$", "", x) + } + if (file_ext(backingfile)=="bk"){ + backingfile <- bigstatsr::sub_bk(backingfile,"") + } + res <- admixtools::read_packedancestrymap(sub("\\.geno$", "", x), transpose = TRUE, ...) @@ -20,12 +27,14 @@ gen_tibble_packedancestry <- function(x, ..., #TODO check that allele_ref and allele_alt are not swapped names(res$snp)<-c("name", "chromosome",'genetic_dist','position', 'allele_ref','allele_alt') - # using the gen_tibble.matrix method new_gen_tbl <- gen_tibble(x = res$geno, indiv_meta = res$ind, loci = res$snp, - backingfile = backingfile, quiet=quiet, valid_alleles = valid_alleles,missing_alleles = missing_alleles) + backingfile = backingfile, + quiet=quiet, + valid_alleles = valid_alleles, + missing_alleles = missing_alleles) return(new_gen_tbl) diff --git a/R/gt_to_aftable.R b/R/gt_to_aftable.R index aa49b776..26367f81 100644 --- a/R/gt_to_aftable.R +++ b/R/gt_to_aftable.R @@ -81,6 +81,7 @@ gt_extract_f2 <- function(.x , outdir=NULL, cols_per_chunk = 10, blgsize = 0.05, if (!inherits(.x,"grouped_df")){ stop (".x should be a grouped df") } + browser() # if no outdir is given, create a subdirectory f2 in the path of the gen_tibble rds if (is.null(outdir)){ outdir <- file.path(dirname(.gt_get_bigsnp(.x)$genotypes$rds),"f2") @@ -90,9 +91,10 @@ gt_extract_f2 <- function(.x , outdir=NULL, cols_per_chunk = 10, blgsize = 0.05, if (!dir.exists(af_dir)){ dir.create(af_dir,recursive = TRUE) } - gt_extract_afs(test_gt, outdir = af_dir, cols_per_chunk = cols_per_chunk, + gt_extract_afs(.x, outdir = af_dir, cols_per_chunk = cols_per_chunk, blgsize = blgsize, quiet= quiet) numchunks = length(list.files(af_dir, 'afs.+rds')) + browser() for(i in 1:numchunks) { for(j in i:numchunks) { admixtools::afs_to_f2(af_dir, outdir, chunk1 = i, chunk2 = j) From 202937335dd7bbde2e8b797696d5246b8804c660 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Wed, 15 May 2024 10:45:19 +0100 Subject: [PATCH 07/15] speed up group alt_freq --- R/RcppExports.R | 4 +++ R/gt_to_aftable.R | 2 +- R/loci_alt_freq.R | 25 ++++++++++++++----- R/utils.R | 7 ++++++ data-raw/test_group_freq.R | 25 +++++++++++++++++++ man/gen_tibble.Rd | 7 ++++-- man/loci_alt_freq.Rd | 4 ++- src/RcppExports.cpp | 17 +++++++++++++ src/gt_group_freq.cpp | 43 +++++++++++++++++++++++++++++++++ tests/testthat/test_loci_freq.R | 33 ++++++++++++++++++++++--- 10 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 data-raw/test_group_freq.R create mode 100644 src/gt_group_freq.cpp diff --git a/R/RcppExports.R b/R/RcppExports.R index cb4f1f51..37fe6173 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -1,6 +1,10 @@ # Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 +gt_grouped_alt_freq_diploid <- function(BM, rowInd, colInd, groupIds, ngroups, ncores) { + .Call(`_tidypopgen_gt_grouped_alt_freq_diploid`, BM, rowInd, colInd, groupIds, ngroups, ncores) +} + SNPHWE2 <- function(obs_hets, obs_hom1, obs_hom2, midp) { .Call(`_tidypopgen_SNPHWE2`, obs_hets, obs_hom1, obs_hom2, midp) } diff --git a/R/gt_to_aftable.R b/R/gt_to_aftable.R index 26367f81..ceea52cb 100644 --- a/R/gt_to_aftable.R +++ b/R/gt_to_aftable.R @@ -47,7 +47,7 @@ gt_extract_afs <- function(.x, outdir = "./afs", cols_per_chunk = 10, blgsize = "/afs"), verbose = verbose) admixtools::split_mat(afdat$counts, cols_per_chunk = cols_per_chunk, prefix = paste0(outdir, "/counts"), verbose = verbose) - block_lengths = admixtools::get_block_lengths(afdat$snpfile %>% filter(poly), + block_lengths = admixtools::get_block_lengths(afdat$snpfile %>% filter(.data$poly), blgsize = blgsize) block_lengths_a = admixtools::get_block_lengths(afdat$snpfile, blgsize = blgsize) saveRDS(block_lengths, file = paste0(outdir, "/block_lengths.rds")) diff --git a/R/loci_alt_freq.R b/R/loci_alt_freq.R index ac40cbaf..ea31038c 100644 --- a/R/loci_alt_freq.R +++ b/R/loci_alt_freq.R @@ -9,6 +9,7 @@ #' @param .x a vector of class `vctrs_bigSNP` (usually the `genotypes` column of #' a [`gen_tibble`] object), #' or a [`gen_tibble`]. +#' @param n_cores number of cores to be used, it defaults to [bigstatsr::nb_cores()] #' @param ... other arguments passed to specific methods, currently unused. #' @returns a vector of frequencies, one per locus #' @rdname loci_alt_freq @@ -22,7 +23,7 @@ loci_alt_freq <- function(.x, ...) { loci_alt_freq.tbl_df <- function(.x, ...) { #TODO this is a hack to deal with the class being dropped when going through group_map stopifnot_gen_tibble(.x) - loci_alt_freq(.x$genotypes, ...) + loci_alt_freq(.x$genotypes) } @@ -32,7 +33,7 @@ loci_alt_freq.vctrs_bigSNP <- function(.x, ...) { rlang::check_dots_empty() #stopifnot_diploid(.x) # if we have diploid - if (attr(.x,"ploidy")==2){ + if (is_diploid_only(.x)){ loci_alt_freq_diploid(.x) } else { loci_alt_freq_polyploid(.x) @@ -41,10 +42,22 @@ loci_alt_freq.vctrs_bigSNP <- function(.x, ...) { #' @export #' @rdname loci_alt_freq -loci_alt_freq.grouped_df <- function(.x, ...) { - # TODO this is seriously inefficient, we need to cast it into a big_apply problem - # of maybe it isn't that bad... - group_map(.x, .f=~loci_alt_freq(.x,, ...)) +loci_alt_freq.grouped_df <- function(.x, n_cores = bigstatsr::nb_cores(), ...) { + if (is_diploid_only(.x)){ + geno_fbm <- .gt_get_bigsnp(.x)$genotypes + + freq_mat <- gt_grouped_alt_freq_diploid(BM = geno_fbm,rowInd = .gt_bigsnp_rows(.x), + colInd = .gt_bigsnp_cols(.x), + groupIds = dplyr::group_indices(.x)-1, + ngroups = max(dplyr::group_indices(.x)), + ncores = n_cores)$freq_alt + # return a list to mimic a group_map + lapply(seq_len(nrow(freq_mat)), function(i) freq_mat[i,]) + } else { + group_map(.x, .f=~loci_alt_freq(.x,, ...)) + } + + } #' @rdname loci_alt_freq diff --git a/R/utils.R b/R/utils.R index c4f90b14..ed5e5f70 100644 --- a/R/utils.R +++ b/R/utils.R @@ -29,3 +29,10 @@ stopifnot_diploid <- function(x){ } } +is_diploid_only <- function(x){ + if (inherits(x,"gen_tbl")){ + (attr(x$genotypes,"ploidy")==2) + } else { + (attr(x,"ploidy")==2) + } +} diff --git a/data-raw/test_group_freq.R b/data-raw/test_group_freq.R new file mode 100644 index 00000000..6bf7abd3 --- /dev/null +++ b/data-raw/test_group_freq.R @@ -0,0 +1,25 @@ +test_genotypes <- rbind(c(1,1,0,1,1,0), + c(2,1,0,NA,0,0), + c(2,NA,0,0,1,1), + c(1,0,0,1,0,0), + c(1,2,0,1,2,1), + c(0,0,0,0,NA,1), + c(0,1,1,0,1,NA)) +test_indiv_meta <- data.frame (id=c("a","b","c","d","e","f","g"), + population = c("pop1","pop1","pop2","pop2","pop1","pop3","pop3")) +test_loci <- data.frame(name=paste0("rs",1:6), + chromosome=paste0("chr",c(1,1,1,1,2,2)), + position=as.integer(c(3,5,65,343,23,456)), + genetic_dist = as.integer(rep(0,6)), + allele_ref = c("A","T","C","G","C","T"), + allele_alt = c("T","C", NA,"C","G","A")) + +test_gt <- gen_tibble(x = test_genotypes, loci = test_loci, indiv_meta = test_indiv_meta, quiet = TRUE) +test_gt <- test_gt %>% dplyr::group_by(population) + +.x<-test_gt +geno_fbm <- .gt_get_bigsnp(.x)$genotypes + +foo<-gt_group_freq(geno_fbm, .gt_bigsnp_rows(.x),.gt_bigsnp_cols(.x),dplyr::group_indices(.x)-1,max(dplyr::group_indices(.x)),1) + +foo2 <- group_map(.x, .f=~.gt_pop_freqs(.x)) diff --git a/man/gen_tibble.Rd b/man/gen_tibble.Rd index 891e1e8a..4b4f5ec7 100644 --- a/man/gen_tibble.Rd +++ b/man/gen_tibble.Rd @@ -40,14 +40,17 @@ gen_tibble( \arguments{ \item{x}{can be: \itemize{ -\item a string giving the path to a PLINK BED or PED file. The correspective -BIM and FAM fiel for the BED, or MAP for PED are expected to be in the same +\item a string giving the path to a PLINK BED or PED file. The associated +BIM and FAM files for the BED, or MAP for PED are expected to be in the same directory and have the same file name. \item a string giving the path to a RDS file storing a \code{bigSNP} object from the \code{bigsnpr} package (usually created with \code{\link[bigsnpr:snp_readBed]{bigsnpr::snp_readBed()}}) \item a string giving the path to a vcf file. Note that we currently read the whole vcf in memory with \code{vcfR}, so only smallish vcf can be imported. Only biallelic SNPs will be considered. +\item a string giving the path to a packedancestry .geno file. The associated +.ind and .snp files are expected to be in the same directory and share the +same file name prefix. \item a genotype matrix of dosages (0, 1, 2, NA) giving the dosage of the alternate allele. }} diff --git a/man/loci_alt_freq.Rd b/man/loci_alt_freq.Rd index 601e277d..7ae93d94 100644 --- a/man/loci_alt_freq.Rd +++ b/man/loci_alt_freq.Rd @@ -17,7 +17,7 @@ loci_alt_freq(.x, ...) \method{loci_alt_freq}{vctrs_bigSNP}(.x, ...) -\method{loci_alt_freq}{grouped_df}(.x, ...) +\method{loci_alt_freq}{grouped_df}(.x, n_cores = bigstatsr::nb_cores(), ...) loci_maf(.x, ...) @@ -33,6 +33,8 @@ a \code{\link{gen_tibble}} object), or a \code{\link{gen_tibble}}.} \item{...}{other arguments passed to specific methods, currently unused.} + +\item{n_cores}{number of cores to be used, it defaults to \code{\link[bigstatsr:reexports]{bigstatsr::nb_cores()}}} } \value{ a vector of frequencies, one per locus diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 75ed8959..16f6e496 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -11,6 +11,22 @@ Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); #endif +// gt_grouped_alt_freq_diploid +ListOf gt_grouped_alt_freq_diploid(Environment BM, const IntegerVector& rowInd, const IntegerVector& colInd, const IntegerVector& groupIds, int ngroups, int ncores); +RcppExport SEXP _tidypopgen_gt_grouped_alt_freq_diploid(SEXP BMSEXP, SEXP rowIndSEXP, SEXP colIndSEXP, SEXP groupIdsSEXP, SEXP ngroupsSEXP, SEXP ncoresSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< Environment >::type BM(BMSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type rowInd(rowIndSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type colInd(colIndSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type groupIds(groupIdsSEXP); + Rcpp::traits::input_parameter< int >::type ngroups(ngroupsSEXP); + Rcpp::traits::input_parameter< int >::type ncores(ncoresSEXP); + rcpp_result_gen = Rcpp::wrap(gt_grouped_alt_freq_diploid(BM, rowInd, colInd, groupIds, ngroups, ncores)); + return rcpp_result_gen; +END_RCPP +} // SNPHWE2 double SNPHWE2(int32_t obs_hets, int32_t obs_hom1, int32_t obs_hom2, uint32_t midp); RcppExport SEXP _tidypopgen_SNPHWE2(SEXP obs_hetsSEXP, SEXP obs_hom1SEXP, SEXP obs_hom2SEXP, SEXP midpSEXP) { @@ -106,6 +122,7 @@ END_RCPP } static const R_CallMethodDef CallEntries[] = { + {"_tidypopgen_gt_grouped_alt_freq_diploid", (DL_FUNC) &_tidypopgen_gt_grouped_alt_freq_diploid, 6}, {"_tidypopgen_SNPHWE2", (DL_FUNC) &_tidypopgen_SNPHWE2, 4}, {"_tidypopgen_SNPHWE_t", (DL_FUNC) &_tidypopgen_SNPHWE_t, 4}, {"_tidypopgen_SNPHWE_midp_t", (DL_FUNC) &_tidypopgen_SNPHWE_midp_t, 4}, diff --git a/src/gt_group_freq.cpp b/src/gt_group_freq.cpp new file mode 100644 index 00000000..cf3bebf5 --- /dev/null +++ b/src/gt_group_freq.cpp @@ -0,0 +1,43 @@ +/******************************************************************************/ + +#include + +/******************************************************************************/ + +// [[Rcpp::export]] +ListOf gt_grouped_alt_freq_diploid(Environment BM, + const IntegerVector& rowInd, + const IntegerVector& colInd, + const IntegerVector& groupIds, + int ngroups, + int ncores) { + + XPtr xpBM = BM["address"]; + SubBMCode256Acc macc(xpBM, rowInd, colInd, BM["code256"], 1); + + size_t n = macc.nrow(); // number of individuals + size_t m = macc.ncol(); // number of loci + + NumericMatrix freq(ngroups, m); + NumericMatrix valid_alleles(ngroups, m); + +#pragma omp parallel for num_threads(ncores) + for (size_t j = 0; j < m; j++) { + for (size_t i = 0; i < n; i++) { + double x = macc(i, j); + if (x>-1){ + freq(groupIds[i],j) += x; + valid_alleles(groupIds[i],j) +=2; + } + } + // now for each group, divide freq by valid_alleles + for (size_t group_i = 0; group_i < ngroups; group_i++) { + freq(group_i,j) = freq(group_i,j) / valid_alleles(group_i,j); + } + } + + return List::create(_["freq_alt"] = freq, + _["n"] = valid_alleles); +} + +/******************************************************************************/ diff --git a/tests/testthat/test_loci_freq.R b/tests/testthat/test_loci_freq.R index 9a8ac109..87b86753 100644 --- a/tests/testthat/test_loci_freq.R +++ b/tests/testthat/test_loci_freq.R @@ -1,6 +1,8 @@ + + test_that("loci_alt_freq and loci_maf computes correctly",{ test_indiv_meta <- data.frame (id=c("a","b","c"), - population = c("pop1","pop1","pop2")) + population = c("pop1","pop1","pop2")) test_genotypes <- rbind(c(1,1,0,1,1,2), c(2,1,0,NA,0,NA), c(2,2,0,0,1,NA)) @@ -10,10 +12,7 @@ test_that("loci_alt_freq and loci_maf computes correctly",{ genetic_dist = as.integer(rep(0,6)), allele_ref = c("A","T","C","G","C","T"), allele_alt = c("T","C", NA,"C","G","A")) - test_gt <- gen_tibble(x = test_genotypes, loci = test_loci, indiv_meta = test_indiv_meta, quiet = TRUE) - - # raw frequencies freq <- colSums(test_genotypes, na.rm=TRUE)/(c(3,3,3,2,3,1)*2) expect_true(all(loci_alt_freq(test_gt$genotypes)==freq)) @@ -31,3 +30,29 @@ test_that("loci_alt_freq and loci_maf computes correctly",{ freq[freq>0.5] <- 1 - freq[freq>0.5] expect_true(all(loci_maf(test_gt$genotypes)==freq)) }) + +test_that("loci_alt_freq and loci_maf on grouped tibbles",{ + test_genotypes <- rbind(c(1,1,0,1,1,0), + c(2,1,0,NA,0,0), + c(2,NA,0,0,1,1), + c(1,0,0,1,0,0), + c(1,2,0,1,2,1), + c(0,0,0,0,NA,1), + c(0,1,1,0,1,NA)) + test_indiv_meta <- data.frame (id=c("a","b","c","d","e","f","g"), + population = c("pop1","pop1","pop2","pop2","pop1","pop3","pop3")) + test_loci <- data.frame(name=paste0("rs",1:6), + chromosome=paste0("chr",c(1,1,1,1,2,2)), + position=as.integer(c(3,5,65,343,23,456)), + genetic_dist = as.integer(rep(0,6)), + allele_ref = c("A","T","C","G","C","T"), + allele_alt = c("T","C", NA,"C","G","A")) + + test_gt <- gen_tibble(x = test_genotypes, loci = test_loci, indiv_meta = test_indiv_meta, quiet = TRUE) + test_gt <- test_gt %>% group_by(population) + # compute by using group map + loci_freq_map <- test_gt %>% group_map(.f=~loci_alt_freq(.x)) + # use fast cpp code (limit cores to 2) + loci_freq_grp <- test_gt %>% loci_alt_freq(n_cores=2) + all.equal(loci_freq_map, loci_freq_grp) +}) From f19fcaf5145e4d8c8598aaa7e25e544add13b0ef Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Wed, 15 May 2024 10:51:25 +0100 Subject: [PATCH 08/15] faster maf for group tibbles --- R/loci_alt_freq.R | 26 +++++++++++++++++++------- tests/testthat/test_loci_freq.R | 6 ++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/R/loci_alt_freq.R b/R/loci_alt_freq.R index ea31038c..be1badc8 100644 --- a/R/loci_alt_freq.R +++ b/R/loci_alt_freq.R @@ -43,6 +43,7 @@ loci_alt_freq.vctrs_bigSNP <- function(.x, ...) { #' @export #' @rdname loci_alt_freq loci_alt_freq.grouped_df <- function(.x, n_cores = bigstatsr::nb_cores(), ...) { + rlang::check_dots_empty() if (is_diploid_only(.x)){ geno_fbm <- .gt_get_bigsnp(.x)$genotypes @@ -54,6 +55,7 @@ loci_alt_freq.grouped_df <- function(.x, n_cores = bigstatsr::nb_cores(), ...) { # return a list to mimic a group_map lapply(seq_len(nrow(freq_mat)), function(i) freq_mat[i,]) } else { + # TODO this is seriously inefficient, we should replace it with a cpp function group_map(.x, .f=~loci_alt_freq(.x,, ...)) } @@ -84,10 +86,23 @@ loci_maf.vctrs_bigSNP <- function(.x, ...) { #' @export #' @rdname loci_alt_freq -loci_maf.grouped_df <- function(.x, ...) { - # TODO this is seriously inefficient, we need to cast it into a big_apply problem - # of maybe it isn't that bad... - group_map(.x, .f=~loci_maf(.x, ...)) +loci_maf.grouped_df <- function(.x, n_cores = bigstatsr::nb_cores(), ...) { + rlang::check_dots_empty() + if (is_diploid_only(.x)){ + geno_fbm <- .gt_get_bigsnp(.x)$genotypes + + freq_mat <- gt_grouped_alt_freq_diploid(BM = geno_fbm,rowInd = .gt_bigsnp_rows(.x), + colInd = .gt_bigsnp_cols(.x), + groupIds = dplyr::group_indices(.x)-1, + ngroups = max(dplyr::group_indices(.x)), + ncores = n_cores)$freq_alt + freq_mat[freq_mat>0.5 & !is.na(freq_mat)] <- 1 - freq_mat[freq_mat>0.5 & !is.na(freq_mat)] + # return a list to mimic a group_map + lapply(seq_len(nrow(freq_mat)), function(i) freq_mat[i,]) + } else { + # TODO this is seriously inefficient, we should replace it with a cpp function + group_map(.x, .f=~loci_maf(.x,, ...)) + } } # function to estimate frequencies for diploid @@ -108,9 +123,6 @@ loci_alt_freq_diploid <- function(.x){ } else { # if we have a single individual freq <-geno_fbm[rows_to_keep,attr(.x,"loci")$big_index] /2 } - # sort out frequencies for diploids - # @TODO get ploidy from the tibble once we have it - # @TODO for mixed ploidy, we will need a different approach freq } diff --git a/tests/testthat/test_loci_freq.R b/tests/testthat/test_loci_freq.R index 87b86753..246bdb1a 100644 --- a/tests/testthat/test_loci_freq.R +++ b/tests/testthat/test_loci_freq.R @@ -55,4 +55,10 @@ test_that("loci_alt_freq and loci_maf on grouped tibbles",{ # use fast cpp code (limit cores to 2) loci_freq_grp <- test_gt %>% loci_alt_freq(n_cores=2) all.equal(loci_freq_map, loci_freq_grp) + # and now for maf + # compute by using group map + loci_maf_map <- test_gt %>% group_map(.f=~loci_maf(.x)) + # use fast cpp code (limit cores to 2) + loci_maf_grp <- test_gt %>% loci_maf_freq(n_cores=2) + all.equal(loci_maf_map, loci_maf_grp) }) From ba41d52396a14696b617d9bf71058bf53f8bb595 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Wed, 15 May 2024 11:15:54 +0100 Subject: [PATCH 09/15] Use fast functions in gt_to_aftable --- R/gt_to_aftable.R | 28 +++++++++++++++++++--------- R/loci_alt_freq.R | 4 ++-- src/gt_group_freq.cpp | 10 +++++----- tests/testthat/test_loci_freq.R | 4 +--- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/R/gt_to_aftable.R b/R/gt_to_aftable.R index ceea52cb..b3e079fa 100644 --- a/R/gt_to_aftable.R +++ b/R/gt_to_aftable.R @@ -1,26 +1,36 @@ # admixtools equivalent functions -gt_to_aftable <- function(.x){ +gt_to_aftable <- function(.x, n_cores = bigstatsr::nb_cores()){ if (!inherits(.x,"grouped_df")){ stop (".x should be a grouped df") } + geno_fbm <- .gt_get_bigsnp(.x)$genotypes + + aftable <- gt_grouped_alt_freq_diploid(BM = geno_fbm,rowInd = .gt_bigsnp_rows(.x), + colInd = .gt_bigsnp_cols(.x), + groupIds = dplyr::group_indices(.x)-1, + ngroups = max(dplyr::group_indices(.x)), + ncores = n_cores) + names(aftable)<-c("afs","counts") + .group_levels = .x %>% group_keys() %>% pull(1) # summarise population frequencies - pop_freqs_list <- group_map(.x, .f=~.gt_pop_freqs(.x)) + # pop_freqs_list <- group_map(.x, .f=~.gt_pop_freqs(.x)) + # afs <- do.call("cbind",lapply(pop_freqs_list,function(x)x[["freq_alt"]])) + # counts <- do.call("cbind",lapply(pop_freqs_list,function(x)x[["n"]])) + + dimnames(aftable$afs)<-list(loci_names(.x),.group_levels) + dimnames(aftable$counts)<-list(loci_names(.x),.group_levels) + + - afs <- do.call("cbind",lapply(pop_freqs_list,function(x)x[["freq_alt"]])) - dimnames(afs)<-list(loci_names(.x),.group_levels) - counts <- do.call("cbind",lapply(pop_freqs_list,function(x)x[["n"]])) - dimnames(counts)<-list(loci_names(.x),.group_levels) loci_new_names <- c(SNP = "name", CHR = "chromosome", POS="position",cm="genetic_dist", A1 = "allele_ref",A2 = "allele_alt") snp <- show_loci(.x) %>% select(-all_of("big_index")) %>% rename(dplyr::all_of(loci_new_names)) %>% dplyr::relocate(all_of("cm"),.before="POS") c("SNP","CHR","cm","POS","A1","A2") - aftable <- list(afs = afs, - counts = counts, - snpfile = snp) + aftable$snpfile <- snp return(aftable) } diff --git a/R/loci_alt_freq.R b/R/loci_alt_freq.R index be1badc8..a4028cbb 100644 --- a/R/loci_alt_freq.R +++ b/R/loci_alt_freq.R @@ -53,7 +53,7 @@ loci_alt_freq.grouped_df <- function(.x, n_cores = bigstatsr::nb_cores(), ...) { ngroups = max(dplyr::group_indices(.x)), ncores = n_cores)$freq_alt # return a list to mimic a group_map - lapply(seq_len(nrow(freq_mat)), function(i) freq_mat[i,]) + lapply(seq_len(ncol(freq_mat)), function(i) freq_mat[,i]) } else { # TODO this is seriously inefficient, we should replace it with a cpp function group_map(.x, .f=~loci_alt_freq(.x,, ...)) @@ -98,7 +98,7 @@ loci_maf.grouped_df <- function(.x, n_cores = bigstatsr::nb_cores(), ...) { ncores = n_cores)$freq_alt freq_mat[freq_mat>0.5 & !is.na(freq_mat)] <- 1 - freq_mat[freq_mat>0.5 & !is.na(freq_mat)] # return a list to mimic a group_map - lapply(seq_len(nrow(freq_mat)), function(i) freq_mat[i,]) + lapply(seq_len(ncol(freq_mat)), function(i) freq_mat[,i]) } else { # TODO this is seriously inefficient, we should replace it with a cpp function group_map(.x, .f=~loci_maf(.x,, ...)) diff --git a/src/gt_group_freq.cpp b/src/gt_group_freq.cpp index cf3bebf5..125730cb 100644 --- a/src/gt_group_freq.cpp +++ b/src/gt_group_freq.cpp @@ -18,21 +18,21 @@ ListOf gt_grouped_alt_freq_diploid(Environment BM, size_t n = macc.nrow(); // number of individuals size_t m = macc.ncol(); // number of loci - NumericMatrix freq(ngroups, m); - NumericMatrix valid_alleles(ngroups, m); + NumericMatrix freq(m, ngroups); + NumericMatrix valid_alleles(m, ngroups); #pragma omp parallel for num_threads(ncores) for (size_t j = 0; j < m; j++) { for (size_t i = 0; i < n; i++) { double x = macc(i, j); if (x>-1){ - freq(groupIds[i],j) += x; - valid_alleles(groupIds[i],j) +=2; + freq(j, groupIds[i]) += x; + valid_alleles(j, groupIds[i]) +=2; } } // now for each group, divide freq by valid_alleles for (size_t group_i = 0; group_i < ngroups; group_i++) { - freq(group_i,j) = freq(group_i,j) / valid_alleles(group_i,j); + freq(j, group_i) = freq(j, group_i) / valid_alleles(j, group_i); } } diff --git a/tests/testthat/test_loci_freq.R b/tests/testthat/test_loci_freq.R index 246bdb1a..82ba0064 100644 --- a/tests/testthat/test_loci_freq.R +++ b/tests/testthat/test_loci_freq.R @@ -1,5 +1,3 @@ - - test_that("loci_alt_freq and loci_maf computes correctly",{ test_indiv_meta <- data.frame (id=c("a","b","c"), population = c("pop1","pop1","pop2")) @@ -59,6 +57,6 @@ test_that("loci_alt_freq and loci_maf on grouped tibbles",{ # compute by using group map loci_maf_map <- test_gt %>% group_map(.f=~loci_maf(.x)) # use fast cpp code (limit cores to 2) - loci_maf_grp <- test_gt %>% loci_maf_freq(n_cores=2) + loci_maf_grp <- test_gt %>% loci_maf(n_cores=2) all.equal(loci_maf_map, loci_maf_grp) }) From 3565340c974e0e1d1c641162dd26730bbfc66095 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Wed, 15 May 2024 15:38:29 +0100 Subject: [PATCH 10/15] fast group missingness --- NAMESPACE | 4 --- R/RcppExports.R | 4 +++ R/loci_missingness.R | 33 +++++++++-------- {R => data-raw}/loci_sums.R | 0 man/loci_alt_freq.Rd | 2 +- man/loci_missingness.Rd | 2 +- man/loci_sums.Rd | 31 ---------------- src/RcppExports.cpp | 17 +++++++++ ...eq.cpp => gt_grouped_alt_freq_diploid.cpp} | 0 src/gt_grouped_missingness.cpp | 35 +++++++++++++++++++ tests/testthat/test_loci_freq.R | 2 +- tests/testthat/test_loci_missingness.R | 29 ++++++++++++++- 12 files changed, 105 insertions(+), 54 deletions(-) rename {R => data-raw}/loci_sums.R (100%) delete mode 100644 man/loci_sums.Rd rename src/{gt_group_freq.cpp => gt_grouped_alt_freq_diploid.cpp} (100%) create mode 100644 src/gt_grouped_missingness.cpp diff --git a/NAMESPACE b/NAMESPACE index d149211c..7bbb88cf 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -45,9 +45,6 @@ S3method(loci_missingness,tbl_df) S3method(loci_missingness,vctrs_bigSNP) S3method(loci_names,tbl_df) S3method(loci_names,vctrs_bigSNP) -S3method(loci_sums,grouped_df) -S3method(loci_sums,tbl_df) -S3method(loci_sums,vctrs_bigSNP) S3method(loci_transitions,grouped_df) S3method(loci_transitions,tbl_df) S3method(loci_transitions,vctrs_bigSNP) @@ -108,7 +105,6 @@ export(loci_ld_clump) export(loci_maf) export(loci_missingness) export(loci_names) -export(loci_sums) export(loci_transitions) export(loci_transversions) export(pairwise_allele_sharing) diff --git a/R/RcppExports.R b/R/RcppExports.R index 37fe6173..13130dde 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -5,6 +5,10 @@ gt_grouped_alt_freq_diploid <- function(BM, rowInd, colInd, groupIds, ngroups, n .Call(`_tidypopgen_gt_grouped_alt_freq_diploid`, BM, rowInd, colInd, groupIds, ngroups, ncores) } +gt_grouped_missingness <- function(BM, rowInd, colInd, groupIds, ngroups, ncores) { + .Call(`_tidypopgen_gt_grouped_missingness`, BM, rowInd, colInd, groupIds, ngroups, ncores) +} + SNPHWE2 <- function(obs_hets, obs_hom1, obs_hom2, midp) { .Call(`_tidypopgen_SNPHWE2`, obs_hets, obs_hom1, obs_hom2, midp) } diff --git a/R/loci_missingness.R b/R/loci_missingness.R index e3538159..7ca40873 100644 --- a/R/loci_missingness.R +++ b/R/loci_missingness.R @@ -35,19 +35,9 @@ loci_missingness.vctrs_bigSNP <- function(.x, as_counts = FALSE, ...) { rows_to_keep <- vctrs::vec_data(.x) # as long as we have more than one individual if (length(rows_to_keep)>1){ - # # col means for submatrix (all rows, only some columns) - # colMeans_sub <- function(X, ind, rows_to_keep) { - # count_na <- function(x){sum(is.na(x))} - # apply(X[rows_to_keep, ind], 2, count_na) - # } - # n_na <- bigstatsr::big_apply(geno_fbm, a.FUN = colMeans_sub, - # rows_to_keep = rows_to_keep, - # ind=attr(.x,"loci")$big_index, - # a.combine = 'c') - n_na <- bigstatsr::big_counts(geno_fbm, ind.row = rows_to_keep, ind.col = attr(.x,"loci")$big_index) - n_na <- n_na[nrow(n_na),] + n_na <- n_na[nrow(n_na),] # this should work also with polyploids if (!as_counts){ n_na <- n_na/length(rows_to_keep) } @@ -59,9 +49,22 @@ loci_missingness.vctrs_bigSNP <- function(.x, as_counts = FALSE, ...) { #' @export #' @rdname loci_missingness -loci_missingness.grouped_df <- function(.x, as_counts = FALSE, ...) { - # TODO this is seriously inefficient, we need to cast it into a big_apply problem - # of maybe it isn't that bad... - group_map(.x, .f=~loci_missingness(.x, as_counts=as_counts)) +loci_missingness.grouped_df <- function(.x, as_counts = FALSE, + n_cores = bigstatsr::nb_cores(), ...) { + rlang::check_dots_empty() + geno_fbm <- .gt_get_bigsnp(.x)$genotypes + + na_mat <- gt_grouped_missingness(BM = geno_fbm,rowInd = .gt_bigsnp_rows(.x), + colInd = .gt_bigsnp_cols(.x), + groupIds = dplyr::group_indices(.x)-1, + ngroups = max(dplyr::group_indices(.x)), + ncores = n_cores) + group_sizes <- tally(.x) %>% dplyr::pull(dplyr::all_of("n")) + if (!as_counts){ + na_mat <- sweep(na_mat, MARGIN=2, STATS=group_sizes,FUN="/") + } + + # return a list to mimic a group_map + lapply(seq_len(ncol(na_mat)), function(i) na_mat[,i]) } diff --git a/R/loci_sums.R b/data-raw/loci_sums.R similarity index 100% rename from R/loci_sums.R rename to data-raw/loci_sums.R diff --git a/man/loci_alt_freq.Rd b/man/loci_alt_freq.Rd index 7ae93d94..a69c5fe3 100644 --- a/man/loci_alt_freq.Rd +++ b/man/loci_alt_freq.Rd @@ -25,7 +25,7 @@ loci_maf(.x, ...) \method{loci_maf}{vctrs_bigSNP}(.x, ...) -\method{loci_maf}{grouped_df}(.x, ...) +\method{loci_maf}{grouped_df}(.x, n_cores = bigstatsr::nb_cores(), ...) } \arguments{ \item{.x}{a vector of class \code{vctrs_bigSNP} (usually the \code{genotypes} column of diff --git a/man/loci_missingness.Rd b/man/loci_missingness.Rd index 9b7ed1fd..4e99c865 100644 --- a/man/loci_missingness.Rd +++ b/man/loci_missingness.Rd @@ -13,7 +13,7 @@ loci_missingness(.x, as_counts = FALSE, ...) \method{loci_missingness}{vctrs_bigSNP}(.x, as_counts = FALSE, ...) -\method{loci_missingness}{grouped_df}(.x, as_counts = FALSE, ...) +\method{loci_missingness}{grouped_df}(.x, as_counts = FALSE, n_cores = bigstatsr::nb_cores(), ...) } \arguments{ \item{.x}{a vector of class \code{vctrs_bigSNP} (usually the \code{genotypes} column of diff --git a/man/loci_sums.Rd b/man/loci_sums.Rd deleted file mode 100644 index e11b2f8f..00000000 --- a/man/loci_sums.Rd +++ /dev/null @@ -1,31 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/loci_sums.R -\name{loci_sums} -\alias{loci_sums} -\alias{loci_sums.tbl_df} -\alias{loci_sums.vctrs_bigSNP} -\alias{loci_sums.grouped_df} -\title{Estimates the sum of genotypes at each each locus} -\usage{ -loci_sums(.x, ...) - -\method{loci_sums}{tbl_df}(.x, ...) - -\method{loci_sums}{vctrs_bigSNP}(.x, ...) - -\method{loci_sums}{grouped_df}(.x, ...) -} -\arguments{ -\item{.x}{a vector of class \code{vctrs_bigSNP} (usually the \code{genotype} column of -a \code{\link{gen_tibble}} object), -or a \code{\link{gen_tibble}}.} - -\item{...}{other arguments passed to specific methods.} -} -\value{ -a vector of frequencies, one per locus -} -\description{ -Estimate the sum of the alternate allele at each locus. This is unlikely to be useful -directly, but it is used by other functions that compute various statistics. -} diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 16f6e496..a3d1b31e 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -27,6 +27,22 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// gt_grouped_missingness +NumericMatrix gt_grouped_missingness(Environment BM, const IntegerVector& rowInd, const IntegerVector& colInd, const IntegerVector& groupIds, int ngroups, int ncores); +RcppExport SEXP _tidypopgen_gt_grouped_missingness(SEXP BMSEXP, SEXP rowIndSEXP, SEXP colIndSEXP, SEXP groupIdsSEXP, SEXP ngroupsSEXP, SEXP ncoresSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< Environment >::type BM(BMSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type rowInd(rowIndSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type colInd(colIndSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type groupIds(groupIdsSEXP); + Rcpp::traits::input_parameter< int >::type ngroups(ngroupsSEXP); + Rcpp::traits::input_parameter< int >::type ncores(ncoresSEXP); + rcpp_result_gen = Rcpp::wrap(gt_grouped_missingness(BM, rowInd, colInd, groupIds, ngroups, ncores)); + return rcpp_result_gen; +END_RCPP +} // SNPHWE2 double SNPHWE2(int32_t obs_hets, int32_t obs_hom1, int32_t obs_hom2, uint32_t midp); RcppExport SEXP _tidypopgen_SNPHWE2(SEXP obs_hetsSEXP, SEXP obs_hom1SEXP, SEXP obs_hom2SEXP, SEXP midpSEXP) { @@ -123,6 +139,7 @@ END_RCPP static const R_CallMethodDef CallEntries[] = { {"_tidypopgen_gt_grouped_alt_freq_diploid", (DL_FUNC) &_tidypopgen_gt_grouped_alt_freq_diploid, 6}, + {"_tidypopgen_gt_grouped_missingness", (DL_FUNC) &_tidypopgen_gt_grouped_missingness, 6}, {"_tidypopgen_SNPHWE2", (DL_FUNC) &_tidypopgen_SNPHWE2, 4}, {"_tidypopgen_SNPHWE_t", (DL_FUNC) &_tidypopgen_SNPHWE_t, 4}, {"_tidypopgen_SNPHWE_midp_t", (DL_FUNC) &_tidypopgen_SNPHWE_midp_t, 4}, diff --git a/src/gt_group_freq.cpp b/src/gt_grouped_alt_freq_diploid.cpp similarity index 100% rename from src/gt_group_freq.cpp rename to src/gt_grouped_alt_freq_diploid.cpp diff --git a/src/gt_grouped_missingness.cpp b/src/gt_grouped_missingness.cpp new file mode 100644 index 00000000..524f92eb --- /dev/null +++ b/src/gt_grouped_missingness.cpp @@ -0,0 +1,35 @@ +/******************************************************************************/ + +#include + +/******************************************************************************/ + +// [[Rcpp::export]] +NumericMatrix gt_grouped_missingness(Environment BM, + const IntegerVector& rowInd, + const IntegerVector& colInd, + const IntegerVector& groupIds, + int ngroups, + int ncores) { + + XPtr xpBM = BM["address"]; + SubBMCode256Acc macc(xpBM, rowInd, colInd, BM["code256"], 1); + + size_t n = macc.nrow(); // number of individuals + size_t m = macc.ncol(); // number of loci + + NumericMatrix na_counts(m, ngroups); + +#pragma omp parallel for num_threads(ncores) + for (size_t j = 0; j < m; j++) { + for (size_t i = 0; i < n; i++) { + double x = macc(i, j); + if (!(x>-1)){ + na_counts(j, groupIds[i]) +=1; + } + } + } + return na_counts; +} + +/******************************************************************************/ diff --git a/tests/testthat/test_loci_freq.R b/tests/testthat/test_loci_freq.R index 82ba0064..3179d14c 100644 --- a/tests/testthat/test_loci_freq.R +++ b/tests/testthat/test_loci_freq.R @@ -58,5 +58,5 @@ test_that("loci_alt_freq and loci_maf on grouped tibbles",{ loci_maf_map <- test_gt %>% group_map(.f=~loci_maf(.x)) # use fast cpp code (limit cores to 2) loci_maf_grp <- test_gt %>% loci_maf(n_cores=2) - all.equal(loci_maf_map, loci_maf_grp) + expect_true(all.equal(loci_maf_map, loci_maf_grp)) }) diff --git a/tests/testthat/test_loci_missingness.R b/tests/testthat/test_loci_missingness.R index f17f07ea..c448915f 100644 --- a/tests/testthat/test_loci_missingness.R +++ b/tests/testthat/test_loci_missingness.R @@ -1,4 +1,4 @@ -test_that("snpbin_list_means computes correctly",{ +test_that("loci_missingness",{ test_indiv_meta <- data.frame (id=c("a","b","c"), population = c("pop1","pop1","pop2")) test_genotypes <- rbind(c(1,1,0,1,1,2), @@ -21,3 +21,30 @@ test_that("snpbin_list_means computes correctly",{ # convert to frequencies expect_true(all(loci_missingness(test_gt$genotypes)==n_na/nrow(test_genotypes))) }) + +test_that("loci_missingness on grouped tibble",{ + test_genotypes <- rbind(c(1,1,0,1,1,0), + c(2,1,0,NA,0,0), + c(2,NA,0,0,1,1), + c(1,0,0,1,0,0), + c(1,2,0,1,2,1), + c(0,0,0,0,NA,1), + c(0,1,1,0,1,NA)) + test_indiv_meta <- data.frame (id=c("a","b","c","d","e","f","g"), + population = c("pop1","pop1","pop2","pop2","pop1","pop3","pop3")) + test_loci <- data.frame(name=paste0("rs",1:6), + chromosome=paste0("chr",c(1,1,1,1,2,2)), + position=as.integer(c(3,5,65,343,23,456)), + genetic_dist = as.integer(rep(0,6)), + allele_ref = c("A","T","C","G","C","T"), + allele_alt = c("T","C", NA,"C","G","A")) + + test_gt <- gen_tibble(x = test_genotypes, loci = test_loci, indiv_meta = test_indiv_meta, quiet = TRUE) + test_gt <- test_gt %>% group_by(population) + # compute by using group map + loci_miss_map <- test_gt %>% group_map(.f=~loci_missingness(.x)) + # use fast cpp code (limit cores to 2) + loci_miss_grp <- test_gt %>% loci_missingness(n_cores=2) + expect_true(all.equal(loci_miss_map, loci_miss_grp)) + +}) From 15e857e1e0da03fd54ca153f2fc8554f9174e184 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Wed, 15 May 2024 17:38:48 +0100 Subject: [PATCH 11/15] ensure we always parse ploidy for vcf --- R/RcppExports.R | 4 +++ R/pairwise_pop_fst.R | 3 +- R/vcf_to_fbm.R | 15 ++++---- inst/extdata/ploidy_test.bk | Bin 0 -> 240 bytes inst/extdata/ploidy_test.rds | Bin 0 -> 102472 bytes src/RcppExports.cpp | 17 +++++++++ src/gt_grouped_summaries.cpp | 52 ++++++++++++++++++++++++++++ tests/testthat/test_loci_ld_clump.R | 2 +- tests/testthat/test_ploidy_vcf.R | 9 +++++ 9 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 inst/extdata/ploidy_test.bk create mode 100644 inst/extdata/ploidy_test.rds create mode 100644 src/gt_grouped_summaries.cpp create mode 100644 tests/testthat/test_ploidy_vcf.R diff --git a/R/RcppExports.R b/R/RcppExports.R index 13130dde..61e67f0e 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -9,6 +9,10 @@ gt_grouped_missingness <- function(BM, rowInd, colInd, groupIds, ngroups, ncores .Call(`_tidypopgen_gt_grouped_missingness`, BM, rowInd, colInd, groupIds, ngroups, ncores) } +gt_grouped_summaries <- function(BM, rowInd, colInd, groupIds, ngroups, ncores) { + .Call(`_tidypopgen_gt_grouped_summaries`, BM, rowInd, colInd, groupIds, ngroups, ncores) +} + SNPHWE2 <- function(obs_hets, obs_hom1, obs_hom2, midp) { .Call(`_tidypopgen_SNPHWE2`, obs_hets, obs_hom1, obs_hom2, midp) } diff --git a/R/pairwise_pop_fst.R b/R/pairwise_pop_fst.R index 46f7d14b..1ba4e68e 100644 --- a/R/pairwise_pop_fst.R +++ b/R/pairwise_pop_fst.R @@ -25,12 +25,11 @@ #' a list including the tibble of genome-wide values as well as a matrix with pairwise #' Fst by locus with loci as rows and and pairwise #' combinations as columns. -#' a matrix #' @export # #' @param tidy boolean whether to return a tidy tibble. Default is TRUE, FALSE -# #' returns a matrix. THIS IS NOT IMPLEMENT YET. +# #' returns a matrix. THIS IS NOT IMPLEMENTED YET. pairwise_pop_fst <- function(.x, by_locus=FALSE, method= c("Hudson","Nei87","Nei86","WC84")){ diff --git a/R/vcf_to_fbm.R b/R/vcf_to_fbm.R index 239001d4..277d6866 100644 --- a/R/vcf_to_fbm.R +++ b/R/vcf_to_fbm.R @@ -12,7 +12,7 @@ vcf_to_fbm <- function( vcf_path, - loci_per_chunk = 1000, + loci_per_chunk = 100000, backingfile = NULL, quiet=FALSE) { @@ -37,15 +37,18 @@ vcf_to_fbm <- function( ) chunks_vec_index <- c(1, chunks_vec) - # figure out ploidy from the first 1000 markers + # figure out ploidy from the first marker temp_vcf <- vcfR::read.vcfR( vcf_path, - nrow = min(1000,no_variants), + nrow = 1, verbose = !quiet ) - temp_gt <- vcfR::extract.gt(temp_vcf) + temp_gt <- vcfR::extract.gt(temp_vcf,convertNA = FALSE) ploidy <- apply(temp_gt,2,get_ploidy) - max_ploidy <- max(ploidy, na.rm = TRUE) # remove NA in case we failed to figure out the ploidy of an individual + if (any(is.na(ploidy))){ + stop("error whilst determining ploidy") + } + max_ploidy <- max(ploidy) # set up codes for the appropriate ploidy level code256 <- rep(NA_real_, 256) @@ -142,7 +145,7 @@ poly_indiv_dosage <- function (x, max_ploidy){ sapply(strsplit(x,"[/|]"),poly_genotype_dosage , max_ploidy) } -# get dosages for a genotype x (as a vector of 0 and 1) +# get dosages for a genotype x and return them as raw for inclusion in the filebacked matrix poly_genotype_dosage <- function (x, max_ploidy){ if (!is.na(x[1]) && x[1]!="."){ as.raw(sum(as.numeric(x))) diff --git a/inst/extdata/ploidy_test.bk b/inst/extdata/ploidy_test.bk new file mode 100644 index 0000000000000000000000000000000000000000..9fd8662c9ec1004609ce56ec81290f3ecc8a150b GIT binary patch literal 240 zcmaiv!43d01Vgp#|9^QZbDC_81MMSW%YMQWppE%4S-S&)^fJ7X^yRVTh*0%RR%-xt OqNEqZF~SFq%?aKr?Es?y literal 0 HcmV?d00001 diff --git a/inst/extdata/ploidy_test.rds b/inst/extdata/ploidy_test.rds new file mode 100644 index 0000000000000000000000000000000000000000..944e7ce35846f5676a4950edc9865b2ef9cf9f5b GIT binary patch literal 102472 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
  • r-&Rb&ouuDR+G>{r@5DU2 zNm^*9cQJV_elPNMKk{`S;~umd9XQ}L?{>t0Ez;BNz-N$W2auj_EaN5?l-rv~-49~A zL!|s8SpF?o{!vVGjMVKIsoSj%{0#G+#Jsm-8^L-#7{hlWuG_HAf3MW}F3fiihR?(F zcVii+oO)l${60hLLHo?TgsvVZPZ!1=!nV2z%gZuG?)0(Q#*@yJy zkiLHAFZw|GZAyLe7!F|i(@2}sZq|BCTf{VHFf3u(!4TYn$3r1Jt}x$2^%3TKtbliC zvA!uH1Lqi*OVE`!IB)@Je7;j=0rTCDVUh)P--FEG$3gKvz~p`lv_FirKZ4;4kmid@ zT`QRWg_!O|nC>y=%b?KFFJZCvO9a+=!}dp5Ta||0PSMzY?ml=VzlyLVAOkgH5h^-?xL1cX!Br0|5U-a5R>=7pk)w@orpJsn?a($s$SzGUcd}ze~>9@9ZQhV82=E0oO%Z`ZMcJb$nnJ<28pL&FRW+9j15Wov?w8c45fRX7-h zS{>EOk*H9r+bjJ!zAZVclCEq+Xi`bNipA1{UyDlmf{3KIz_Ok*bf|PEZ1pH-5skdj(=Ds2C-shLgo;7T)Be3FVi_bFMgW=2W)u&(wd?p+g+ zlqPr7%qh87$+=aV?&H-;UmdaZMsRs4c~eqeYrRtTC#sdbhM7fvEi;L{^~o@iUnl1a z>>RsRjyLWcyKSfdLvuFiSO-HeG9@d;r0+Ldu`=0PEG3Vx$2G)(p)!2#REQ2uCm!clbE{E^jnx-#ZF44%f~k;iMNI%Zch!^r9OL? zGg~S%qZ}Cb1?uXvjhW?OJBuTS`q9rMhdW~ObElNv`c##E5qY!hE@oC$yIFKql?|vW zR~y-`9hkIpm43VA?tAdGRgpjA1K$*pbxf7yV7gS<0%|H*DGk|l4~0VC9yWSSi zGP4}*ljHMh{C+uJ-Jk!Y(Y?duzo@czfEi`)Nj3eAa{7!K-z~@AVUoqLx~cJi_a-x) zfj)Axl-@e5q<^o8wo#`3uy_l-cw%F02Mc=d#1>?Kb1}yuHr1+| zi!JnKkDB0xu zUJ>8H{Ee?-BdfaM)j~5uY>2f`gvG{E3n$cM{rofP%gYZ4ynN%T^&#;* zi$8x*JkMnQ#yJZ`Uu@hH8|M5CZ+s3xHr6$X=lP88?y^22-e1Y+ejz>^6?>2%T%>7J zZ&qsI_z4LYVz>mu#hQ#{RtQO!@-jarP%I~H(!}J=;un;*f%Gd#okiO;DQ!dk;@(j3 zGeOrf46o+peO$a>iQy{#{uAQ;HT-$KcuuiMvi1p@zK*i?Nr87YhHEig!|{2WFV_(| z+n9Ot@^_g1dhY9Z-lx#nT z<2K)Z69u=!%|8;{+`_^Yi0F+b1+tan`&7hU=}FJlI<{hja$h$Z<&Ji*1$sAQxIHF+ zw_j5J`Zh&9%LRPfm@i-OyfY>rc3x6G_;Oayasl5C40o}S4fwlR_<(<@d>F+9L|d|1STFCapjyqBiGgnjbK z(I^-2-Nf;KTHrqzjh@%h^mUd^>pbE~DA0h=h(HfTh^;=&EOA*refhi%-MPM3} zzXUW*1G5N->%%}l&;tp&!wc%QWFqgC1m+`%=LG~7YG5&er6I6_z|{tzhY=PbtR`>` zf_Pp-K%`$wK%`%XAl|Phuo2-pgpCAlAg~Ex3xUlDTM2AK5YIaZY)9D1_!uQ%H^Lr- zT?*_Afg2UL7NMKK0R-`UGl7E!I85MZ2plJH4B-UhdtU|I77=b$;O;7Liom@Q0Q@>V z1P&o&5bP=-()B3N9|HFw2pUgUfdYZSDo{oc=_?UIl^1|>1~`v!0pa-s9zb}|01t$~ zLj*2XfkzQUxsQba#5MU)%ORsUSM|YJi>$!EQTPE*b{{)&>#l=htHiFucZm7$SvylH zmQ;TsKIV&kxlB4Q9t)0L31?IAsEhvzdBl}14#AltOr0qX4CbAx;A5^(vHQHV4W;xs zakj;;yx75z&y~HK6MKgWnTj9Rl1~@{bDHMbTV zB+6%$k!eSew@OiJOUTEk=7Ol~b~@H+yW*hDr!%&6Z{|Bh#|Hxmj2JM~-oo@Z(!*p7 zkb^-cj7PC0at1zwu>>mt&8ReBVh4srE8>8O>XjJRj`*%({t6M`?qL3*fi5h68kRp5 z(@ZC6W@4Jzm_`_lbC`c%0McJTdfSl(Fcxnk@dd;Klb*XV{yyekig^|D4+$k1H;$mb z+psLyCj|!L36lRLzS~adp_hA#IxNF-Rw#7=UiM?#+=qDQU>QrX?CDrG7?)*|<|Mw` zPWT1&Uxi_cv>ViYE#rd_@O&MXw-)ONbY08*!yePg`}O4a2K)_%^hTtA4fEwUllZc@u`4vF;!@Ab-j}mAIE;dYysmO*yZ{^oz_b@ zO4g}zR&~$s=auI_*YSqpn`R)zAUR(&l|%7Qm+=H{*qtHAU!mar3sc*g>Sa1{VyKtO zzb}T>YnA~I#4E>4e2w34r?X00 z4>fRJ-QFW62y~T3Z13T-5bIBdS#W!g<}dEHn9mBjuv~9dqlw8|&BUsaSoLbexMw^X z#o@=|z+gI4Ih89^%Jvx^`vJjGfo%#>0E1h@PAp#7F#h!^ZW<_!Uj9S;{(mZ{#pf@G z=T^dH^~TM9#0_S~-ALC7!g*2CHo_rMmS}shK0O)Y?zcG>N6UbVOgj?q3m`>Hz@Lf7oIzqv@!87#QQGhZ#aZ-I`cO)60LAb z(F)2w4qi@z+`Km(EAne29=)Luh6kQ;!{T3xcrl#@-ioX6L_4iuemlYM1{Q2H(aZRT zM}OULF7r1$ing14QEntEe`1YF`HQ_SCmSYJ`Tj+fU$dg|U2DjM;c@u=Rq_5xg-`RD zztNNG4U5SeJCn@cw`q}ba3ks0#q%N-X&v&jG=06-W21}~i#Z+N7>&_F;Cl<=ON4;# zayT8du<#FIiasNrMH_Y!m>L2z2+RtBxdg=8saX5GQUMV^pMbdBO+3?e&m@9a^IU|m zgn-B|o<$n9Y=GrefUcd2wOaAK65$#GB8_-nNkCmI74D_A1l09WNOvv58U@6FnkVYmjYm%-j}TAv|hG7cR)u zeoMy%6Z;ikXVp)ODN~xmYP{l$yeW)(liY7J%EX6$tVVHQR=t5maZIW=uzX9`xzsS+ z5viUBRzDjYJS(XHPU7VUu<-O{Z2P z67Jj4-sUW-CEOUQ3r)O(DsI6lQdgR&BG1P~i?Vnosl$|ue!ei5n#P?GciXl%#YdNu zcMM_{i*zhQaz$LiE!VFb&2npXk#aGY+x1eoNQG-m(BHxIpPpTWvYN4)A85+fnhg?fgj zF};^a1>q%9cfdyh>SYohoU2&0EB_ZV{Z4ruP!G_`7_hoJ)OzJUPC)^1%e{*lK zG%%D;JCcFUb8RtxGH|wZ43q24H2^4nChP?|xsCx7P8~Mk)R}PVxcM)!Q%APlN8gEK zeM>EGZ2D}hzgFYNhO36ptr(N}UoEWNFT>|v?u!jl8&Nud~w~c)g->%{r!{7ar zzf1|35Aj3M?jEM^4F}y_Uh2jyzs{0eY6tO2Nb8!0@>eZqhP?M1vgbmnc#cW-C=~vx zVV~51ukBjqY7+|(>3nU_tA%D0w!g`bCj3pH?QUmW%PnO7w*?nrC4haM%>TyV9hkNO zTjL?dHIsM-4Yrqc0QI}lF;zI&HapN$w^hvD@|=LV%d zzhwTm4gnXO&HS7~3$4Qze76;&k@2qk>>=aIf-d*WxkB! z-gmdnWB6yo&!ra9N>15xD|==|NYf{n*?XmUXG?0l;(dKgjaR&_hL4F917CPjJgt#D zwYoaW9$4#om4+@thj#_C!3C?jaDNnq5L0uo*|3imTTszd=_nJ5f!(XabH!=2v_YlbD6o{DDGb0 zvQL*&c>9l3z>iHGCvHW70Oh#vgDSJT2vM6d=>lBW>0WEpdm+gQT(Q!-3$&a(*KZd@ ze6G-!Tg5h=k5=5+>I)_wTNu3qN-!o0v1m+lPp{` z1ux=CSd=cbkeM5GksPmFMZl+Ty816R)}QC2>6b9Qa5QJ^T%Lc;fF@rWlH6LOBwWUf z?W&bAhSN`#pSpT3ufo3-X8z3#tCM2&Qmhb(dbhCX7oBstw0<%i zjc*?P^pJhMmTc;M(Rmy>m|MsCL@4)1qfxF{O0&opd#+$XB{d@B8<5WDmH1mx$W{FL z2jclk=HDO?ff-)D5890=@;zzhMEwg%~d5_-2Z7mtwetzfX$yi!oe* zp=j@GFuWQ=zJt{AK7X}3W;?IGr2O{lcZtiLd3nm^mf>Y6_f?}&F5T9$3d1$g`0wRg zb7}eTy317Vk4B?hz?b6qy)RF!jmd|#b)*OJSP8_FP@s{3xJ|{hcZ1%#Lic-^_H#@j z>ADDry(M%{2f%a!GpoQ{Cf~dwb}iB`P|PPF;;&-S_HhV9U+m=&c@_~+>#^7XOAy4Z zD$58|yPZXVxNSw`3)}x8_`J>lYY2>_9suSo9HgD_R$pSs%4)h<(m>U&WQBCEjm4S;E=_eWB6AfCLT48t0 zF5GOS-yFlOIe9~j^-6fxdt3e#^KHU( zTQJ;;;Wo^-8S`yt!Mz~6Fx-vd9?Y|o1^0sN!!!pl&3@9RVlPNH8_8af;Nqzzryt`x zK!U5=6T3dx#IBF1JnS&n=f`UL&Job7<7bz-{E4j|)wI>bRu8!Tt(?Lw;$vo~#|?_J z*`;qBZuDr$X?Z)b&qLuwT=|O9e-;|)46eH`lIeW93Z1W1o~y2;FOJE#B}TrD-b%W> z(PUyJePShjVkLdFSJMCQa5TP6tfbec+{?O>J`&>waN_x_eY~qF5p|t@p}ut zOJnk3X&vbic)p5w5(-Q#_)aYN3K%O3zMCV$4gxzPf>^8tzU()^C@zpzy%th@mInA5 z=UPax@r7d$(u3U%{S@RLXa-qn!Srp+UuJ8^xXC2n6y`7QB{9AO;}!4en7!7JDb4^B z&C2phfM#WR#gl{PT=`J$e6FA*S5{ljsOXf{l{S-aK@BM$Wp(AfI#vZ2QLKM3`%KKM zJ8Ec+;I=X+{3q)7sAcNN{UMykwYod7%d`y>+D6meFkZ)zd@9TlI(|tL!QOc9dKe-6;&CK7o1EkErcS4i3VY+ta z?`aUmdIy%jk>rDs3I=u9b8X{Wqw*{MnznI!P zRLB6Wx8*ARCm<3d=BkxZ6U^ob;Pqkqd_|3((`Og#QZBPU1pl0JC0)p-OW6ZFof>~d z!Ra;TB=h@9#i79i=5pp|o!GNBR3tr=ulPjML{rPfp;E@)M{QKW@4%N=XXr47ckNoC zsuc(-gU(6SIc<^!=d~IO$*Wg~`}x=tet(TTlXnKOf1|VHD6>Dt>OcD%jTtqz#LrB# zFz@Dwv7_Mvig*mtIL5TF_Oj7HuM@Wl*TablhK^V~fS%fl>D!s_M4in1a}Xk52gXBL zNsRBr_-Sl>I8il|A>+4UqBvWX$x)zg@&t%`2n`dQfOl0fz-#udDqho2@2b3o-1@$* zyvyNTC6gP^%L=@$#mkEQVMJb5?umSEcv<-*(D$e#S$sgTYevwP8`3m@<`iO0~m8W{V>Ho0TW3<#Z!LG~{JYv=Oc zlZG*l@#p4YKQD%^J6FNsc1?EzhiQJ_l;&TVwRjl)Evm@2q_R*uJ3Bsn?UF4eRirsp zwa1OoI!!EeOe}P;@wm{zz1Yl8A22=@L;o_bYy?*mCRQ_^ORE`ym4}HH3^uM-FjP|g*qbt8vQ3z56DFIsya|&{ zQ@;t5ZNg*&xXdP-;z+_p^bw9XY5`9VYYRJsdBRPHhFyp6sLxnBve7)$WEg7%j*h7i zveO}=B-|LqT=)$x`iWIRMf6XCv5_N>S?O^g}?ZmmV zD>3aA1cyko0G}6Pnk1%KMC!Bz=~+bRT#D(IG1+v1>+ImlIN0)WH^!g9caVOiCOw=J zE91LmSpQX+?;6avn$WWb@vS5Dti^QKVmgT5fbTYvGT}FvaB%J0D;XC%IM;O_zJv6e zNP75vGtvrX=o>N5Hj);2|0cqFDp#usjXMy}PN&H}f&AaaPt zpnJaqpF-a5MY{JREeA;XLS7G$^1CtJO&U6HBIyod9S`ej4*VuSz*t3~fxG!E$uHT}DkkfEpzAl0uHQmBUX8dM-dI0XXn!Jv_TNTYY5%9PBgPX- z*G59u?=ZbQy-w5izpH8cHz3W9&Q1tjAZY%*5Srh_X--TdG*83w{(!VQW8;rhg}CpLb(D-izV;upaNn zdb|hoe1Md(63h6IQ^p~MrVnBICs|NWe+KV`mreIE0E5%c^xhJS(KUt;)W z#PKDh0mg(S3M?fd(D)Us+gGtpUvuj8ZYGa2-@x#%Sa6*AI^i?O8MM<>{QZ9B%N+3L z53r1*zQ4tMKf!eWSE(z|26imyirU>?LAR80BrCRCN^yf#yYg1O zuupPBY1~8AwjHW&z3wV*ut?ho4Q{YN{S-IXK{Z}+Q&rUXRylsaR3C@lV^Q2#fLFQk zwy2gX-(G8NH*^Us7vNiAF8>p1`77o0 zs*UjRYUN$c%yJ-qyqD$G_*Kj(C!f&C3H*s;Dpm+xt7GyZ#fM@{{U`L^6sqI@xz z5PxCqbzPPEUvI2`z;@NjG2pquP=7-`)piW&C*ke8sY?Ae8|xQn-_W6GOSJl_W4l_v zurEGn>u1u>TMhKM7{%(L%|FPLQ}S<<^S@oizdZuqbbQ(&S z#(_^;50hYboB13V@NP2jSvP68*_{4^YDbDArY;Sp)TyvV#ill<2jQhJXvbHBNT8T-wXar@0NX6!csC(3a-y}GyT$!hX; zqDuX4W%_|&O1f}i@6cdAmq}ObtcFNkVJDecxNl?8g?nz+D?M4xI}%$+57_0+ju0zI zZ4FxDB@}L6RVQi)X{nhrT1871_i=GyysPBF8j-Ydmhw?-X_)66WCJ#ze4)cA~C?nyO1E63la#@jOf zNF#k%r&yXRoK5F*S+_GJdy6IBHHg+%I*5v3IYm(RFqAbYbiC|k(X~6x*T>9#w4cT6 zgSnb`I@&$$K4$7^eu=HUxl*}uA~#?w3B+tM%*$|EE}*JcDlerE7_VAAZW_D>B37^k zWa@BQsvqPp$oW;T#|L!vF)_RtmEl0o9*vbVXeLN;xh4|T2s#sypvfLVd+lBYM@hnA ztygGhz-M=-BBU?N>EEPFuhOiey%Lf3W}y9KIei%JCc2_{ZdUk2Rfx z#d0~fr=d7ACC*Y=xTyu6VY5s1 zdz}1w;+;r!`HiK|=BMx&_Stx}BPlY&L)-N#=i048nz+hK~u9OGq0vkv0OJUr^c# z(k~@E5N*|@v=#Y_*=F2ihH@5TxSW^waq)f`hAa8|Pl)#`FkHomu0VkI*I;-xhA9kJ zb3AVpc-FE=GW!XdzFsmLrw`Y&T)?*m!*#rVTSYnRWAbDDW#UIY%LRPb@_dg9{2Qb3 z;o~&@<={g-%eBxT1-u(Dygnv9*I!aT_;Og!asl6U7~a71|3u8Y;Lr!LMSfzo3B5H# zJQpsnrNyb=E9zLkaQfAA;b!`QuFWy)vAK@*h#;3g9F1}T-zE&V@cQ)(OMKKzI$Utwv9(Mi^{|;W>|A^7CU z4EJHU8^aqhJb>X%7~YIwH|LA@_2Ywt-{Nutak_XB(l#IYJ|Ej;8EKCu#B+#0KO}ff z`CaeD^I9$mKFpupWt2Gbb(q!v^4oe}`dbS>j>O`JKad_72mBCtCs)TiqH@H=9-<#x zxR3>Wx&_0d7#_#)n48Y>^b2eXh#-$2py}&5V4T44B%woGRuZNMs60)={I{|2{%_o0 z5T<;D@Gina7qD>Ub313!?NpfKbcQ?l^TRZMCzEHP{g|%p=dBzp&n<^eFfnk@QF2wW4N>v0LR*^{HAk zc`vKYtm$QA->j*}NZH4NBV`xUk905sn)-=uwCiVdwDbE&js^Qjm%?ZI7qP#LvR1lsWxx zil!xex~cXXt6IXsT@@B?HvqcNU^v9zPZIAd7@ouM0^vt1i}wYc=ksU$f(|c3)NwSv zpcALvF0lHw8{j*S;R6`nkKtJiAL8YED{>EFcrhB?UcQTUq+2a_1t~Xy*$71s3u~FoUNscaYxy_fU+{V!;H*EKnC?{-(jPAWP%J1Wr zEB-`%9>wql7{WHJ2z>YS@{8H!k?(qz3;14y;Y%=loa1qKEWxVPD_LE4GU4Kaypt(H z{XKi;73PhB;st$uAvI;h7>F??C1`ukscds!XpABXRkxZFvjQLcqM-2m^) zWAgpwb)*CFB%VP$2@Nz7Xfi-EfmQ?1o2@$#x@v$90y7Y15}0j(xk_4qD?;EZ0?80q zOkk-2mJ<*&)D;AlA&BRz35fL95D@8CA&B=W0&5Z0A*>~^p1=l#>j-Q_xSqfb2;zA& zflUaoqP&&BHiYd6TNT)0fSm+(hrm7sRwL{uuopo*-$BtdIy2GA&BR95qM8T z_#lCm2p=LKXcEsKCh*ZJ@Ck&E5cmKBT-E$X2qNxN1U_wmKPK?mi123wzJTzhnDB)d z@KuDb5%_ux_$C6)|0xCj0O2VDPa=E^;oC9bTLix6!1oaZ?jO_us@%U)fuDpxvjXHx zF>$}j0-wlmzraJD|7$#aqD%+$OL|5vNvbvjNMgGTY;N4@D?xL;$FPCyF+m; z?(XjHTHM{;p-6Ep4nc#v1rNbpmjCX2^X=tcWO9d`l<9QA&;(98b`9~PvDPK;yz=*00f{^xi2MjE@JyX{f`X^ZWuVp z_TXeX=k77rN(Tg9#GdigWY3&Z292O{nWtvGKzadA10sVR>>g*ck7C%Jl))wxfBoej60TB>aG1)_ppkBx-# zLnT9O61I6weq_(-ko8W`64P2W$@~}^u~kY(}TNiP3z(-Kb4bty3V@E4Zlf7_RT06qqd*U z5WnKU^DG)DtUkOR>wK2RE080`l@P=&iE8oC3UO&v-qIpXdi_4Ox6%Bw^oqFEqjO{V z_@wUR@8oHJ1K4gTzPyhOM-fSLpDXvdu zxtY{34F_bQuH$b0aNPdH7*NNZe@fHV+PID513IbE1%C9pSS+Qe-owyO$x1XQk zT{e^c75wWT8&dGG#A+BxlXZ8AP8k(4)q5QC92G!hye#i55pFDPm4;3L`fGhq3EEYn zF@Y2xm|*@S4jyhgR?Z}se?i}ppTK$oC$VXo#qYgx{QI4m#p`B0U8yop+)r=T8BBub zD)HjWn2g~*lcPF?Hz2DZ3pt^|l!f-p4vTP>5!9$VW5R+*?MJ(0G`IyF?@7F39 zATlVQ5<44LForO!B)=d8ji;ieM2UqMXUJlmx%h1GlH4Ol)>eyeunq+S;o&Hf%FJBM zBpXbhI&&Yzh8VHtf|HIb3;S(5j6HB>Qk++{aXs$5BC;AoD_r^-{B==f>&vech0ExM zk{C|#xt48xEMRi>?KgKcb+e{85svMO&Sg(=mwJKtj-wu%_}4^wOV@I* zHqtJtIgq6O8QUa=g(x8^oy)Jpn3AmjM zR;rv=PeOkatCMSAQt*KcJ3UksHqm= z$ZhM!4wnB`R@T@;PDJ@^+K~c{`mDALuf3CD!{~#cJiawbTPT^PPMeF zdY<|gDq-rTPq@SN<)5(PFN8(pdJtH6ZJK7qPk~^CayGM})ANVQcPMHZRJ)Wc_p_Fd zD1Dc;teOl@r32T$MYVb{C%@gvyl#ne#q@ZGPjDS0zzguF(V(SrGs2KA$_@t}D$5(p z==mOSrE|dLx|4>TH!k7vPLoZ!op1!=YP>^2STS2FBZP)mKwJF>gPB(ymlgtt)hi86 z0IfQ}^P%8bHHVg{8OblzzuvG_23Cj*KqW%(Xgi4s&oslzSEwyYH(;tbk3vaL*HjpS zGn-B#g%y6~HCLB2^i4noUbbCS+;=(kM*r+YGM*69qu14$)H_j#kFlX)tVvgepeXdc zi1mipfO|d9)3<_}{I&k(E`dv5=FSXM43disYDWIAM81`t#KgFWUciow&cvdHQ`oZS&@bswe+xB z0$Hq&(&yFd2XCe|!xiFjY_u(JCFChKs{qDo>n)b493uq#M>5PQDG=CIOB0I6P-gfr zXGt^ydJeLJ>I{J-lx$&GWL$v2Q4I4{Ih6v-(b^(tz{@)FuyYz|FTfPmcwF2zqyx!5 zokFv$%-KxDa$3nCAB@6Lp%><#f3PO=xuu;Zy0M>m5{wrCzCNFk8mc{h@HWomE@K>7 z)eAr0PM_9UQc0d1iRf!=`;MDLM`BRFyeMY&jLtBcSX(qvaP{Wfp{O)*h&cj|q63!{ z-+-b8j$bNOQhJjx{~@WpoBiq^g_}Q93EuutUfRg6Cq{yJY=m?LPD|07&eT_tC>(sMCQYk6g_d4*;qZz{Jm z$^6Qm`afk zE!gQwX;0#zLz!6fJ}|x1RF-T)h5I__RcpE}Gw&*mLzlj!sTDNdIsxW{pe40L)A@vuSYZyBHr{ae z`N3gl%}lPe=J8xvJfe;zu2RwNv_HL*B+4pc-8WDF>3yHUv!5jKs6iJw+5bGB--_KR zKFjW#p)Cnej>1OKC|c>YC29aF68CFcP>LT zJxgYw%Fsdsg`wuqpCxR>SskjS^X1Ktb@Smu1weE5tFlz9P*1BAvfQ^v$;M@XeU!qz zJu#c3;+bs*3U()YEiBru=Uf*TX84O)h3v#D@3QL2^XZnSZZ02N&O|Dgp+m+^3&=T+ zrW^>9D$hy{>p;?(z>-comJj7R+Ww?s0a5cVZ)(p@4YTRqDy}iSko!lBN{O|X9azWj zqeRM4sGit`mf>GDk4f8{h*;n_z3jgpmlP_uR!7{yBmb z3t^MjkZHfxhZiLaA#OijKP4ah-9cmCCxv~GbDLrqZ>#T=S0K^|c`3N~8u)%jq3KXk z--p7!ZFa%_N67uLNA}nEJ0uHJqLMTyh{OIQ;nK#}^X;5-?_Sg|@83AOseE#m?kz+= zu6;b3P4g$QBKGrUhQOQWqV;-3#eUT~aDef_=(lEDN_h`WpXL+-N0eV~;WmysfQwq1 z`bzO(9^aQsFrnFnl{@`;Px@sNmOoLQdvRsl#7>5BA$C+`lMP;?i5lP9Q`BbjqxLN zAEBFeVY52UozjB9461Lc-T5Lg&e#L9h~eqN)@ab(#tUf9SKPXr?RMHWkr@3pDbT|p zh!J`5a?|*pTD?mC@oQ?@2#u(dyc*uakvfh|6bs{yc-1X(wx6k*C_C{S+-klSTQ?owU!a0@CvUl$)9hNs~gO;$gBhf1z01hhjR!{O<{7o zzuH5N3CdAoiSZpzg|`xjK67&)!1K@EvOjff(*-O@{a{+M5WDp+hcrxoOZK2*+%z)V z=U4e@kWl+eV-vF4ktOrYes&y(Nvr4EqA4;PA3~J(Sp>-nDs?B)_n*$K?Y*5-{^jA^ zBhdZ;y7Z=?7qPWmNbjiWzEfu2We6Eg+fuz>5Jxb{y+Dhe>ke~CJ)+-HqjH)$lxd8% z?%YZr_SMdwOd?AQU3I?jJD0R%)TxX(q&(`89x;I}$7?d3QCDdL)l%~|Jr^%?DVqTJ zW^312OhPMTZ~}t`aYY}>4JFQI_4re2>cApb4UQ6vRivoN1k;#W6nd*pcxXR{XI^UfOW)U}M#V$u8gaAjqci<2@njdOlvIIcw#sOP=m9Xh8yx&W zRHOBVldoSr+t-zXD$DX}h?oGo=GQ%YjjD)gvD8R!oJ=vEmm4a4a62n6QFkQd3^U)J zuZ4~2DnXC&xoH!wTvw4-w_&`kIJStU)AqG79oVT@935`^DU-II_W8_Catr%m zl`YCp>wL__O_7wxL{!IUsQQFf_F1+iUA2xHFY9-*DuU9?Lp{<7t1=;Qv*}P-Cv)3m z(L?TwpTKw^zd+lUeQhQ*13GuiME);%ncog-_3j8!^eOdZ4eI7n<{$fOtmWM=CK;jz zTRZDc^cR*xz9w5I{)~H2Cc@fAWIxUVDhq6j&d`HAJpFW9PPm2IhcTqv&-aNYR4#xj z=Y$J*H;UeIKZ3%L<0%9T5_k?{bf+w3I`yktC7AU0$G79|MheVz=7`x;;MO|m)PAVP z@e$27tmj5{bAch>kf6`gI0N2cw8^FnNv$4K>uMOSLYxkeF+bc2XdH7HE32Y3D2>0X ztH7|ZIq-RG5vM3$+^<2588{pID_;^K{CTs68J8V3CuO%$n?=@p*<%-d4zt$g^r^~j z6ja>8rq^R&T`+KjEv|(&&6nc~f#9M^FnLk4R!_d}?=I7Zf+0es`q(~GMVaSdJ~G31 zB8BSDJA!W2@)p(=wj^5xEywIjFR-v~ixQGWHP+|%R~lV*!autWD)EdLF7eCDhrhPw zW~>)w8MnrE`iUkaLZe0XUW=Z$wbdO;r&NdjNMH4fo`J}%|GrkA0S!To*iYshAnv#E zylHKhD)3VC}e zAP4cf^e2>dLN$p&BeFSKG98ECsoPs6<|ewjZG3FvQ7km$C}d&GU3}-ozgTC4EgLm6 zlP}2i8Lez|P`JI_e~qb-hvWbIIT!29rj>Hsx#}Z)@g1}yA?_tOs>6O1WbeWw!hq-D zkuCO{txZZl1KrL~a6L_g zZeoI5#jdeg!YALc6a!5R-ct;^Nl{n&W6`3e3u!qwa+c~e7;irEO;@Yt6JmqMYk!YD zX>ineWk}x_?Mu!8htd(Y2vOW_%6H8uKIqQZtadZ$3H~=nvQ5uA!!0^hRLen{?FJ1T zp2EZqv{9XSZ9;gX4JLOebB^cO$d@fuXzv1-MGj&A-Uq$)K*d(-`h|lIM5!N3^jzS~X1bv*hkT zrrefETNTKE$T?`b;HuIm);{<|h3Q9V*Y75;pPwd%R+J14w4`(ZjykI1XBH9^EQ#3N z@3E13+>pte$QfGxv9aQ~jIHLndpy$s`#+CC2CkKYYzD;ndqHJlNJQzd3iI=~#m~?o zmZ(;uGY4or#`J6C^1kNQYmz@@_$X6LLljs!Qcyp06Je{LzMjk6@FW()J~8-KSvI$c zo}=~H^|(=6r_hm%=T48&d0*8rvr6R#HYfnNG1pAAJ8F{1Akrs+Fx>MbF}jM8MIY+qy;#{)lEzzvJcbc(&tMjGRwp$RaCV2>wv}d zt=n-?y9ovW-4VypLtxx^Wy)VZnX)I2lyoA)t@yQ)6I&ZNJEX7+R0}CGB+p$2hm%m>*Nj%x+YGQMA`BStBUF0VXW$P$N+jy&N$Gr4)Ys+*a6NyMuug>(Yox{vSc z)Rut~@!R){$PP9c z20T-1Xp2%h8p?t;2lxcm`^xAzJf2nxlcyniKg_eptVuU}@DjbWTdGq~}fRet? zrJa%@I4KL8tl$nMhLfoAc!|935*F*^5@nEOh^C7fzSR+-y_KO@hr+M-ermE1h6#Ab0b3n-9K5nq~jaH|5?VR zD|;7Y3RO>^a&+4DFl$QERZOPW1KWobGuvR!2b+0Iu*y^WVBCEWh{V!E|LSBYb1zZM zT%GyGBbm4=PoI**q**M>yRT6oJVGr|w%z$(E3PxBoeh|;{e99q6Jw1Lv0W$gJdKu}ln7p<|wXa_D3VXSX*QG9MDhsl| zb`?)&u3WzSvj{o5viZ=QTsIf4ORU(Sh1|lLYslNZ*qIihd6~2CpPlT?CskpTglrP8 zJT_KYK#eP4B(5u!wp(_EWpV+c(OH=JR+P=Ga^8T}vCtJpLU=_eIJ1Qc;cbRI%Px;V>)M*zkU$5ZLVL6zoG<~CHH}=7vF(|eImi-U*VTx~U8v35HXcIt{t54uE0b+` zCS^R-Sm{2N(3MiRmtEyqcFy957GTPzPfHSmed9}&bHBC;VpZ6q1jY%rDY-kM@=hM> zXNIUSbm7;=Zp9B{A!BzTIuPuRJPLf923>izjnZq3?ng9sQQY^Cb?P$qp>M%?6dG}s zZzDN_oWo3crNdM9+i$}55&0I*W4YHe@CDG&2tPYJqG&tTnA^ zhWqkn0^#H$iy{?c)k*J4T^c2Na(n^103L&JeSBO>oreg5j$^YvU1LoJsPFJQYd-x> zU{>Ob@C$y|Y)?9Gwi=A|xTe#`=Ns0p;S25`RA?b`F*hg`#e+Q4@nKMEJO-PHuU|&- zwkzAZ*z9OhM?_0^wl1m7UtvX`6IB_d7afd2K40I zV77`Ao#sc``@3P^G1ac(CKZr^xm0N#Osl;eMtc1G@ea6J>4N|68Du;}(~(!d`se_q zd&I@r+tyZ@BT^W#)mAy@#A|6;QUXzSI~OPP#9P-1M!2Yxr)-h!r`^jEj!jqelBClf zv*hTJZm7yBauctJ5mePDdVHH*&i=T^#Fqr@9DyVU}tA$uW_p z)`{&^4uO7}*0N*<&4{P-P2@l3j%C&m5nl=bbUHh*=$dBT;*@ zg5VDPygvRwsrlsqjdNS+PSB?0Z9oChFQ6G<&xkq{>V<)JHB>@PXD7^=tvu!B&(Z;4g*y(2AEItiOKi9yd-tu|&oK+BW+$#m!w5=M`<Wsqj#Hi zs3_Raes;VB0~SuYR7lSrEnUa@5?AdSbux-_e~^#eJ=B-vtMet+chYrU zfog@;3j2po;rGex&!d`UCo9akb=MAO)y8F^9Xoq~Wb7HQZ}~FOkH6sLKLg&FDd~pK z)JN)Yew=-?Mg23${EVku0;&E^`5=NQtsPAAy_$@ERZ)w*bgDD^wHwm_1H{2vZNwOG z@1~|4B1&9%<^Z!VpeBn|3^g`*A?#cJ7 zP)k)+mqw%ipo5xzFM7Q#{?T1?IJVBd5CrPCYU4+(bIOQE#V0CuA1&lXqNPL2AqK%g zeIXe<`U(mOPq?d36}+_L>8#(-t z=Lu)~9jS<@K+pW)SeNHdf@q@tsNJ5p5RsNp%uCP=0!UB*AEb;D_1G&E!?5tZb^gA% z*;8~U-ZGK<)~3Sb75T;?TCBIgqCr$K1j>k4Lzqs~=ObgF`tpfEaBv}BNXZt<^PVfp z?m^6q5eecC1L4hR1{CDlxmMwPmdJ68X;E8Nj*2#Brx#@*JMsRk?Zrr}_B6Kje)6jW z%Stb>c}k+=fc!N&p>-nRjRDZ=DU1Oa;GfJW$GUDNopS%c*dOZ+*S&FyuT1IdLx%8E z4=XRnaeb6xHHJr7V=ekwoMDpA-;TpoCqG>j$e0%sz9)#Lsh-I`q%D28wmm5wJnj5? z`u!WM)z}OSOxl_29a#ZHo;-4mkKWrn521{_UQa-?0IXHL$npQQ$zN3W{CAl=lLsw> z2gV*(KsxnwDe2xgr#jc-FLk*jEvMOe(FP9f0;dxx&*UJ--wx)(W5pT3(6KpkvOHb^ z_T%OUdd$}eyBxRJn2moeWSoZ~^NU{>PK+DXbwU?3Lyr$k7LM6QH(39Ln-8vy=97FY z`E@m{tU*X>1AD41swt@j{9Thdn9qc26BWP35f<~eAO`QvIt#smBio@u3!^e~bSvud z&`5hnN&BeJ870pA?QD+bDfxd>dEh7smyPjTIV$=kQKE_~=g-5`Re>4Y>|ZS-5#$l& z?u?9g(VcdclEwIU*LNKD07Urh7j4vIaxL|^SlEWeNIIU_d$|O2HHNw@2xuC$`etRG zpV?c$#cEw}S1S#whWRThmgv8k=QY)A4@6U)8Hfatj7lDbqYf}&$wL_EOyx5y z{v5>R59570Qe4y8<*J&78(q`;@8z6UPl$7t5=}qIIFogY74WKdSIPLB)-Ro`a51=(aMmP|or%xuhI;ql>Ev1eT zYF@b`W29R3NF&$L{JfTzL)>PQ=OeYJ=~iXYuBz+A`|%tNYWZ@tf_#bD?tJ}63Xfo{ z3Zv9urV0Ky(pYTvive5kpUTy$Pk(y{2VD?@P(wdPA!{}+(;^GITGMROP zDD@r@s~y0y5yldvaz(IbS4@S;S|P1dk~o7!83VWL^z$5|$KRUrEMBj=!+In*d6E1q z1>-xzdfK{b%}V3zA}T>6sy|JvGV3!{s~61#*9~|Z02(Ik)BR>Wyo}+kdW2YY+1O-~ zZEKK5z}~v)=1!&r_@`=Z!Y~__Y6aB&G{;|W^9-#oIYQb~kS*dn;CuD;^Oj@QC4E=^ z`C+zRdb@e5*Dh0v=sQuK#TfE)X_;~m)t7E|YU7EDPb=GhQrnU+sZ2h2Rz(t=51utA zT(EOHpAebZ-}-`l+RZp1A(nJ&KYCmO>}1CqkG+@1n?tSTct$?l)`llyL$NeU67}w6 z9iyYEuaP^BH|f;Uk)c&jE*K)*S-=CX67WVf$G+{=xM%SDK7hnvTu?nW<-1Tm|p#;@T{txm2mK% z+nLK8PgvVw|NE$%kuX}d;_Z++*R0#5)b?+YPs20iiR}!gI{Or7JRtW+e!9sm^2F^) z$UR6EEm5$FT|RK}~b~E?+gRYe$?3F)|jVK@F6zgSz>nnf@`u{B^Fl(gdWyTf4>F4*z=xnXoSP<3CYL7@2faN$f=adT3`x)ZreVLs^`? z`~1XTe<&y%R4&#J6fT%WCU-KC+GH7n{OB@W)QQMkn88YuyKDCL&r}`6`cdmpv+$mv ziyTmY+j1#-w5G;yx3A*#izx21aYt9@=%8eh$1P?Ze7gfdtl zyGfS19JCmT>y`Z=AM74qhJ(yMNOl7u^Ot&&bznvpfj{pUQge;*+qXyC7@c6R14H6V ze?}TK|Bz#E*kLhmqYg&HbyxZSfQ}0DW~941KQ|frJ!CJ5YA6{;8q-(<7@T0sbfhh{ zB44z^Zr_M|;9nFlnif#^3EK$vAOjwO)Z!;S*r!em2YT_h1`)m++kG0TPvy)IH$boK zDV(xHQ~<{AR`8BGa;p(fHb?WBu(rE#7FcS65&kXd0{*0ZEvWU!?RxBOGs1cIZy%xE z^$2z%;dL(Fi!X9(u~rVl&0H*uT=}ND_Mh%Wf}feograsBaEVQH8RN+FJ{Yly-|+rI zwcBl21If6XW!v2SFa1aQRQdC5P>;MgRy_kje2D?7aXP84_uLo!lpFkmiqE9YF5!>P znG&03MCyvv?84dvz<-sA6my#s`#mtnPYJ_X8B05nZbWJM1I`8VorK(zZ8@NZ_0QB` ziG7k$s8PW8aEm_xvbj%P^d_49jtB!y87(D+Vn<(ygc!LmeE-E?OQjbh)sjmsL2C|+ z;R*s?lEu)Bc_cGy;u(#J^aH(!hi>96=lH{|^h2%mM}uFFDKqYz?#B3ED=$MM-w#IM%S^6rc$GE_wDVZ)_=}6D zRr4AJu}{$Q0bF;4^6C4@GJ#?%z~#cZO>Dh68SBv@_Wzf*SzK&H6B}?*f8*~E;L?j1y1#`UKst7e6rG3|GFr& zjYYy?Ya${dI9}r#B~8*IS3v;5oK;<~!yIZ<{7f1avYrGYG{b^!EQZP?ro6I_sdm>D zwHlqlrDPT8Jx}`$na59M{mOj&apBspNe-0FdrI#j@DkR$DMEQ@%X4VTW3fw16jp{m zWn|95p%JIi(nyV+(Z#~H;{&t+Z?ubYY8+j+>cJqeG+5^asg*P_&3!FP8$iwlBbF^i zsO%K~>$DVSC**XbXch(?IRsu`OZGi#J-D4DHoVb7pJo~>2tUCBD6$*f4ZoVQSuqC0 zX}|rWCDfxBfseDJPupc!04}S?Zc?)PyZOX(_M%QJ<4q%}@}{$NSGbpYd;0hoc%mJ) zhR35BzIb8e@_MHe9GUG1lpI{C|B>2rim0PbI#du4GHuF8>ueMRafUY!7;3g_ur=u# z4BTAJ)vzY$!k<38mem<6^HfXo^Owiepk!!5&EUH4$6Af z1H(>1&Jd+J8yuYC@a7zXjCK?*>B~rQ>lbs*D!+v-hs|ud0)utUdIyX16+nE~~NDd066 zF;00u9u9xI7Wd6A6YO9h24{IE^lILI@~!Q9^=CZF_!e33-(5DJUVjLy@6W!FUXA)j z=z@gkp-%ZCkExswQ9inY{QkmJI6jQxjhEB{u;?aSANxF=i>t85pFy#y2%47P^@0m{ z6S(Bh74pf(FgG4u(t3n}&2#FW;9KP5(k5W~Nb-BuaAmo`BIbqc@YgdF7L7$#*Um4x zKlJdMW|i5+Uib!~4M{x`o>Rbj)cqcm?@g_%Am`d^-YsQVf{fkL<6KO}Ye62}=zF8Z z-IPSeG>qFm4OFk^tdl}dM;ALa#+KDnQ{x!5B^>t3r!-Me>B*i*rHgYyC?Btp2CtRX z*YLn*x@6d>lGW);D(MRi`cTuBxmsNF7O0>t(9EaXk5lFdf^9wGCV_N=xu<9Rqq(%x z>+TQSKyc^-Q*v(2<%BU_oelVWffd;vNG~TeyZBuBmP=& zjgPEx>AcCbX%K6KIIw_QH{^(-_^zBt++|Eee{>t%_Q7I~T(47H1*TU;huK3^ip%Anx8;`dv{=PxOoZbK2r>h<hs38>dH~Z4hbYdoKX#sCb3bM&$~PGNFFyhrxH6X} z>WvB&(LX5GnEvm)0 z&V9ZfdbpA3xsUd<^K_Ja&}1Xt^BbwKVsmJ&ci~dci9)CAH*{#h8Sd1+8+3GyzY1!G zEuRm*W<)zu{!>%imVrY0`cX^v@8#o6LLGP88j9|#I?W#hBaR>&^ucRDluBoM4-h!a$b(D~)JchsQ(%w1UD=TL$#>zGjNlcZ3R#!$5p+u+EIf)>6$ ztdGL6b@6R?s9FRM@*FZpt|0c{qX+?*IhaNm7!=MReG~+(6Y`n(?~WVgeO96T|2_p7 zWBnKkbV0xrnsFf(x!8jWx`sKYzA%JoGYFC->q8>%i>~gI{nEt`qhHmBB{)MEL>0;h*n@shdlsY)_$)cmFPS4RZ#nSn7_K6g!+>^7O-RAKC%(gJxUW zg10r}x?rM-*Tv`(QU9D6#IYL;_|g$u>V<*2gU2MnRXgylmWY=jf8W{WIGK}mPy2N6 zTJJuM_&uPWw8dStd|pGp5Re{+$qKve$Qxs&TgABws=jq0eXKKSLlx;-u`)Kd1xR;m^?W~|GTZ~>S{&vCHU9BB_|A$`Rf|7dKE!E0)eG!Lw zKGL!IyAAa_&b}+&V%;~HC zaC}r0yVu*4oov5L`McIpCgNy5n@Pi53FCJzypsBeu`5Z?2A)Wbv6Nu;Psy#yk5lZ@xUE`C-I8f^^m}+gb{grsIjKScSRT>t zf_Rv?V5^xXW9;&zZ2MlrX-#4^M)sN-01_xpE5{9!=Y5g`ao|soyUPT0bk#e$Yg8(V z6y&HdRKjqMUB+i3lsu-mln041j<>7C&xE@Q)>EX$r)ZPRU8;h9X-ZwvN-7?Xd15Th zSR}{L94ZYc&Vrm}6hNlOYiaS`*#qMEc5b<#pT!7u!t_d4rF z`5>!A3lZ`!xNWH6qbT{bjuml|hJtemX)))Z-8nNEZ&5^$D1o4oo_^P#KSl(gk(CuJ z?!rc$HgHW&2(B}KjY3(_)fqcI1=%8OkRGp7Pk z{w&KKvORIu=k`zn=v~I!r~78QW5P~_vDeix#M6&QRpVUnpAYkdnQAbTVV~EdQpbV{ zohesLsB_O{#ue_cWu+gA;*$}2r&YoTfmVut7ZdbX0Imw@`V5L1biHvt@ zf7fk5H-pz1OzP`=%IsKdU;k{%1sViC_Xm;-+^1r}5SYSXrS8T$zXVF>N8(HFth)i| zMXp3-PWhs~HZj)0(7;p08Z3n0WNW$#5KEwK|3cLc&xNQ%IFxSfk6K$>aW7$pK~h_r za9!hC<5hc`Ex0eiH)~oCZ6#O0pNFVyxt}`?%QL6$BbpWIpQ^%A^7P=I#-2O2pqsP9 z?t9y^2rj@Z^J1kqbCA?Ioru&Mh#C-*d}#rAnR(WpXO`QGX2^a^aB5e&Zp!V%3XWoCF@X9KZWFLVk*=?J{ ziXqmx^1z+4cxxa;z+rv#1R7J3&}k3vf(>azFnJp` zMQCLWFkuPh%U~+L=!zB+qE_&P9Juk&pTLz#hW1?OXZ37Se;+hA&Sm^M5m2R{8G3VO z@`g*bDu>(}JT)d|5!1U&>lbg3cWX}1R2U=yB@b6%U~k7IO!v8vju(YFOc*D4^rJvS z6??sFqwi&n;gdN!b1Dy#NJ*}kVIGuga?A3KUn!RD7 zP#(D33Q$G4$7Q$42)mEOu%U7S@qx=^WO<6DGbpDga2|;gLjh=o)e(UObRpk2ExsVE z9!Yp|8HNRDccel4#`xzkBp18 zs|Z4AGD!xowCHfIra2x-7zzgJ7&b;+Df_=@ytWiPPYM%(d+x<@mi9()2C5c=)Yke< zqg{Ehm%GsO6u6$h+C&nPYX#t)j-Q`!o$WBWK2{=> z+my6xUm>bfOc)SGrhV$xEG%dylu4~kR4H~0ymRV(&prsPYyMRu&$_t0c9jyR3Z_E{ zqIB>=(iZIo`S|_40@a#FqNp7V20hW~0M2JpNg_zDhMTN!Mj!TC#Qx5ZaT$NAL&Z)x zvhd>FRY(_#fG5olx$fv=lQPa37)o9Sb>4pF*sa)j5_Vv+0!^O|v31X)i*Cq=Q}z6f zh1v?;QoTTFb`Tv9n!N*u27I-@yF97ujBQX~l>{n2(=SpgbjlHjxkvs3dZ*D{oC)(+ z9te!PAh+5Th9`GZ7#_Ahd}Aj05a&b1uO9t9~kiGy0%TKU>e_w|lTjHS%=2RrCG%kA%2=k+4+z|sljJB<69Byou_Y~@jYcDxLVJ7p1iDfC%9238$&aqS1k_hh6zLti$h%h_rGJ|;tGAsFWGnV=@J7!+p(zyZrucVZ}II( z8ed-w&N~>N97-(l-tOIznKZUVIT}}Su`HNisB}kGZVswC{P)9XrSt!HNPwo&{SHF9 z!7QO1`43#hTr=M}>!vkMyb{SZh^|{n^5Iua$-KtY=#A(8HL4`;shC^>lh-e9;ogYB zp3dsZ|7DO@3a69lmi|4Bdw5!#joCr#vL{+ zM1u}}9t6^v7oB|%R!sYZ3? z$6a3&(5g|9`P~Gm?_B`!e)KVM6gSg(2;Me(H(f<$`-GxZ9^F?OkwK0WCy!{4u00BHW(b`bfFN8|5k*deSYva;Le> z1sQyM`m8}PVkQvDZ9mfGVeE;s+Xn5_Cy7^Pyz0X|NrMbA)Abj*17<+H)W*k*Elr+~ z(}1TXdg7KF;oRSuM(>OP@V=iV_pRhwH zd`wYGM_bcb7}J>~vxqHvjP5C};I0Caj&CPrl4YQTd1_JuG! zplJTODdSKvlgS`|q|0ECKOk<$!0;g**B{Tx_ls2^i%}$$tTY0 zY!AT2{6dFKXwjqafb0whX2%NX%^yQz2?#Xf2MzjVUPPZ{_nP_XYaXQ9@`q+H3a-rhl0ag8o52>mGl`{rJ{nMxfYIQ{E-89JLfb7 zp9{6$e0jRPS6(GkUy`z|l@aR^Ilj7LvC%EVI=^7>2D^DCv(Q$3+vHE6OLqzDp)Fym z5q%=#(Is88BZF|O8Y~y3p zuFg;a%Nx~o=_z39r9 z4VH;5m9Tmrv$u-9YxseO4#i=DjO6q5Jvyo`Bhn#{z|7~_SO9mA)u~a^mOWcO^xmpG zoZ#sJ99K3lz>&h)yYeOnAMKX+NBVVvHbl(%MT`pL@w>mBS%vMMg>cgU=Bk1M#7$l1 z0@4{)K@|H9(;8=@*n-o_I}M0L5oWxM5)0IlF^{7E#oAjy#qqT3zJcHpAV6?;cbCE4 z0s#^{!9##Ra2p_KAQ0Rc+$9hQ&fo;M;BLWn1{;{+PX6C_&fe#qefM2=-Ln?c^y+Tv zeYbhg+?x%xm&3x#-8E-`IO<&KhC-t6Rdtqt>gQHsTOB?+4h~Q zF>Z$Sk@K!x|Fh!T{t?P{>?OaUxnkgmez z9wQwufwWX{!A@^-R^sG-w@lN#LL1=-t&hyFlF#3^<%_BL>DVc2cbWj_wum2@?VFi8 zb-WQU(A9j6(^O=jAQ@0<8(^5D=F1V-g*D6RI72UNRimq*ZTW4cQYV^Ve_!n4hXXHI zo(DK-hEVl{W(@bCHurr$k{;TM2lU$AKYI70w7bNuYl~EeJUnYCiEu z%@DbfkmxKL0Htj|P0GU+1g(_~04&GXODe${-sC`zwfoA+t6=KKFdf-f5AP*P5I6Kf zKb@=?cJj#sCTDNne)1iA?@N~PaxLqXFHmW~(mA9^rs4q{oUwa`)SeprxAs)gtx`-m z&8v>0OYIcgRi7@k_L@$Lt|~=6B#~{No&e8M+r^~1GpvYAP~rWRaHV(5Ru%OrU`umnwv7lkIx*h zKs3HR^GFI%N;(3#4U9gL&^PJ#Pf5oFxcmqQ_=A>rN{eq*p1BpME`)3fF~d~|IWb8H z0(BMLE;X_-etg`H6h|T56n-P7Z8<^wnq76x-m0{($Xl|bKCf{$JYTIYr4H3StEY4gdd_ zlEV0Jq@>C>tkCL|E6gxAd73xJa4!{JWy3lO;;wK@Ej#pjv;dQh5_U48^L4aDJC~7v z1)G<{HeXnP(?P6zR4;mNDdPqMHY4p7(1#2 zRN1mA@C-ca;N4g?ll8*MPkTR#MTougdGarneV6exOy$65&x3UBE;+W9)s_Qx?gKs~ z*%rxU5>7B69=>ge?0RP&FAUx2H`A|eOb3|X zGjcY5Y4v>vYdaaaUB~Btz+b~}fZJaJ(=Um7+OTlVyLwV4PO{aT4Bp1gP@HZ|5X?N} zXyt}qTX#NV1tR1oe+Q4ga%@Wy&g;LedEx^ki!1_Bov=_Y#aJc%(-Pxq zw@sRd{mWm4u$S`qNz}C+%8<*(E3aoTOwYgbTREBT_kP_4=^)STA=GMp-y!zpV6qnk z7NkZ2O=860-3n%(CXrZTCe=c-B5&>JY0p)8>@SNsIM=ub{i)7|I>8Hn&H!q=+AH+T zh#tSB>B0?HrD-}}`>0HTfw*s)v!(B+b~>s=f0U$s4g66;%VbN38<%XZ1(@!}ayVpb zovHy%O^u&1NxV2_GRFZOSoR?V<5a(B+GYE788haKoc8j^Z#v@PN}DSbQFVR%26iPp zi#h$V8@ON7$GBR$AU89=y!>4Cf0LA=A%1bn|6fQ-C8>^5{L=OGAdwD~1-ekN+D1vn z4Mfs!H!PzYfxVsv>Ae?_j@eXf4{%jJmB-^#DO?`$u8Z@i+^wPCW&Pf+D!e

    Upj2c?|=;@(a$-TB`LOWFTF2uqDu{+|(+@;>OSio1&Mz0ou~Et+DV@5ylqzS$MH zLtIKuhxk07pwA7%pu`rY2wwAFeIGo8CW-w2WM6zz-U_2f3-t#+zp*;*M`bKCWqJzK z7;J8lE_c*-5v`$Jf=Kr@5tIZ3(qjkCiFP5YY{kvwDvqvWRN3lZOG-d3VY;eAi)Iwf2v?Rp;uN4sW>+M~kupxUrQ#l-)JwaMQ@get~B z%|bsNvu8>e$NsS$C!6`>lTA^y3q%|z5`pdY2h*Y@h#8>!VPl!x6S^D96%pS-4D;0F zl0~BQD*g&h0Z33uQTK@YAQ0>o%cs(6a!^Pl5iR4#sVDLDQ4M_b=Vd4sY8xhDgIHL8 z7D{rzejr7=Ew%FIM9CVYX^CD1em4j)Cf`Uwv#Z;2qRE<|%Z8j2q0-3(AH_am$@c|*=_-IG zDg9@{?O;!-#H#g~me^BZ%!KZfp!5l?LBTend5=V+jm!|YcNSu%>VM6T9{oJk+S2dV zNlATzbG3hQ-O7M|zeRz7139PJGQeE)@w>b|!kYYMUjp8Y?l`S5oZx7E5{a0%YQ~wV zR?3}e;J}-=%A8HNqY(n(*&uZ+(W+SbP0#eViQN%t`T*i9f7FTU94%O(V^BBhyqe}b z#Sg4`ak1gMN{bj7bsf5UlVesp?lX=?>0L_4tIni~`R82oT9vy<$D7)ejAf%1Sr|4A zoRcQ@AaIS9!k{J};Up4Nk90x;RTV$I)J_<|H)~sE7_4l|&DGBZS8oFwzopI(jXsT>8G!9pOsn0KyA-FAO7mLhzg|eSg{cRjw=K-HxhYOXPVWgy_UqomUWL6lytEh4 z#l$^4M@~SBc?e(tB#&~sa{IPBh<|*QUNZ$b|G8Kz=c$t_!|yeEVb+w#X3Yk)Y1^#u>H%s)2OkWx*gP;H4Wr1iylX>V9pGP@g34hB51io&Mlr?ou zx?+6MIpik^>{L&WF&`D9U`ye0fTW3sId#mzUYD8V9hYaP>PGLq;U2z>0|8&OC6AZ}HPL)^ zU(VS)1{4Rx)W$A35Q4aEhPIZT!iUEE9>eP6}PE2^pgLk}sW3%bXEJy!+QW z38{_w{xt3#=wktn142dn!=LZ1wJP=hGEOpLVk63{{FX#AS*`GsIQ?C)4(dDRuvRS= z{;v%8fLSE1Ao>>+q0J^+gfi%1;u=qRgpV=g0UI zkR9G#IBHO(fckDT)Ply&<*`A|;_#rOfX?3OU3dH{4&U4dZZc`UJmOS=_0O~FA5kpM ze{0!yHaTK!We)^EQEkqRgj_1PhVMNw9Ou6;I(HpD3 z-qM1=1(RQ)bn+y$Ty;xLBs1J>SElNROg^3>C2bLt7(Q=rG0i_lTx`;9>264=fu@Tr zM{d9D;b{^4*^nGK=%`+2iGz|KlFLbTO(!E!_GvL1jU#htyFuC5`fF!fTjX) zuf!lJ$4c{9U>@P+F{@*K&RW0C*}lcjQP2)om0piHniSD9@iC;>72P44U)RhX#m*v1 zQE3)Bz>aI@npOYibNVSE$0JrM5H}S_GDFSeDW!7gRrwUq+s-C%cCw)I-T#SnzW4uE zcj?E^--Uckyyv?ZKBQ%d=cJN1q2~muS;pPbDU({DtAVx4b#w3Ns>7kB38JN|Rafah zqa4SrUiUP7W@tu_k5uq6d)w)Z!4=+P^mHw!A662D*A*AILAyh3uFVA-&ED?A-OWEIMe$|UrPbP&>WhSll znq7vF)_NYtt8rQ!RnR3QcT6!oM(vo{aRbhY)REVErpVo?y&I!TXrI=y-%u2Q>_S7@ zl4{@8dFbxMtMT&BP~1k@2-4CR0vKB+Uk9BtCB~$y>;B$E{?2$a#rsx8nF_XBQ~Y;| zg7$(bab$v?ra|{mwv8jv=|bme=4hjQUKls5N~HSmQgxTc`3*#!s zb_4Z!_*jaK&v?=RCPiGX_(Cz~CU(!?>cl^*tsh}JFHUsY_1PYDccJ3!3V*g})B0OX z*ousENJHS@KGdkn!_`l`llIJqoSf4*5q|K)OYo~}eeMr?+>SI&2FDJq#EW~~qTxC~ zbzMHrh}iIjZPG7Xh#Jq}@8X(DeZ&=a`w#;HgTq2J@H2GQuE*uw=4xK9?cf3Dr8h+- zkk0^tD7aF6|6nA6Ffd$NY!Q~?aYr$UVh$M?H{dh5R0k# zQjcP|^PZ}GV`AJ8cA`Ihx93#I2povCxM4U-e=*eUQC{EQLHk{wvi^Wn1SimcVgI0* z{b6}z)V)DBjv&C^2TVd& ziShNPCVIN0J`9Gsw`DQm@yah+x%*hD>wKd=O#9YGomnC580*vrm!)@5ny??nq!1;g zj?!z((6Baz_6fYvs6I`=V=U})x{dvt>+?%7gR!lNHG2#@L0|?b$j(X>;w>Z>1;Rd; z6$);nZP&Jd2(*7Gg~$qhWQkBmtlf`xq2iG_F1@`V{G(YK!x)z(=42F?UtFY8nzZNzb>ODyb0<~> zhL+S8Rt4&Bn19_g!JGdWuy)dO^!f_l74qgbqRHv*$FgPp}F3s}W_PCoDcP4br;_Z&S@>p*7{3|2df{YVi$-S(7f*}i1_9zB$Eayv$OvLl+ zAvRT{1C=-N;MuC8CqJc3ZQ;QYch;QmUkj>`S=T-9Y?=*ov=Rm_)%22(H)wW(kF=bv zN&dO0#@{zwToZ0IP$9^nEh;Lm$oF(ZaMG7G8P7%(Bcj(~a$a8kH^|ilB4|pmM&!#= zt261n42s*}AaXATX9EK@D;>C({KSopw8Q#6RjALJyp>ds&cGl$N*mEN{jt84CDuw= zS0mOcTA#W2mc*fqK_oUce#kaj97DEM09$GBGGk9Q&Wbs@4pFlhYA{=AWEVpiUZ^>` zQY$SwOmRaNO@8};Q;(kiHTp0XV;~D-*(K`7@h5vky!4Z8CI8b{nR}^2|4BS!lsg?R zN|e)Oe(JpjT330nrG&4lhl#xM?RrU!9;FveXQ6BYQF?p?WV4 z@41K>=7aoza5|pol_jxfs^WT=RHHPP#1s6BRl0(Y*>reEJ$NDcR9dkVqu@)$4{IUz zgVgE|Pm{#{%pB?Tg(P}~5@B?) zlemWzT1Yc6Z|eu_0L%m9_tT##ULyO0?}$qNb&+&ek$ijnEmPI~HzPZrnF+xt>(0!! zJ+6938%F*t+wU-0e<9bCO(Q)@Hk-t&p0Ey5WdO^RiqO}<(LLcFf;UNjcDA6 z-*dN>IN!6rbKX8T%&m{N+q8Hg4L4Sq2IOla%@3i+r2^}aisYpJ$pI(@cj>%(+n32! zd^b6ZZzuhufG%{_vHy}6J@g8SS-siCvd!rijQD0@pZ;CP%0IVPw1VE1*NRFXjse%+ zZ^G58UGHNcuHZ_23yZ8X8#Jric(bpUpz>=+dElytNo__EM~8Dnc)jpf8=2(oy1GMz zAkEBxU+7MVH=p}jN5tJ_Kq&ZA?_l~0@Nnu6Uv~qaa%VT`Ed9TL5#kj>eFuLh@(9E8 zzwkYIAuWEJr8e)%KvSz?_RO^k#MuBKQn+_zs&05RSR~rUE_PzdKk`nu$h9bSykDPf zfKz?5Q?+Lc<0TY+w7?p3-4bRiA<7Il-5^kFQ?&Rt-^5>2`}nN|CVn9t9f`O%+M|h8 zeq{+Jz3BLgK3ME6ejsKAYkCXwwt{&lUi*s8Z9g{Mu<3b`&coT;&z#GP=O*9dW1&5N zFFfKvn=m<7FLs;3x;i&*37DyHB%_Vh5o9judy>X7D=XgtC+iZaAdZU-|e0kvYTBDO>sg5DM zQ+`v7gKoVbARy<|H{>8)OVVQaV8Ln;`uqYE`4Iqj#<65sB$I1YrtbYt=gY!Yo9mI+ znZ-d9-?LIP4cYxh)l|QZ_9;MeV)#hPrk76g@@|j>5uGw|HZ23hk%z-*SMT>cCM@-C z(?}4c;Q(d|06o=5Epq!QpBDvR0R`>w^h>B8ude0OPuX`)IFA@T+oQVapt{_*68I&L zVDvwB8+&SlR4om3g1`&JpELS>*PsDt2X@7oZ_MdbAi*##j(ng>hpXTl_B}fGhZGiq z+79izTNV;nF?WdpI607Nv<^Xc&2nx)fEmhP;=(q+@{h6m*y0vr0i(S1Hr7l?3O%CLhvKGaP5bbMFIj;HVx8u=h-Y368M!{&rk-D7mm=6!ZoSN753EN${Ez(? zZChQBshmm%T^gwD&m+Z_GW?%vHch#p`W*Q<6y~%re1}k-jN*q~P@PnL4Jj8}dP9Y- zc-6IZOSUv?U}>el z!Hfg7T-uKd+mM)-L5*Hh;cM!~SqC(-EiW0;>`%VoHZnP>X3KO+KsGDEbI)2A+{TtD z?7>N4YxpkL5b#2V-;y~zJ16c=(ECrgcdZNmOh=pX{QT_Cjl-$H6a3lgyR*Fjd`C|M zO;63<5cMQ7Bg}bd#Pbe|Mr&{9NR7th1&5{0)#Bl^<(&>bciw!1Hq)7il{M3@br%gEW5b7m(-r85`R}5xrzPzR>P2>+0Z_ujetA!zz&0Y>2R-Uq_yphsu6N3KG*Ck7cf*MH`*7=dj= z!0~?9<-PhJy{`7I%j1DTfE-;@o%`qe|W(Z|G?p9cTFd8 zmwajWo8)b@qB9`3Spw6}>cM%$v(HUtigXHbMdzQn!11?NS9Mo3=&pf$O-J_v20<&B zi3s(hN4N8NF!2W8V3Hk-S_+<=kb82(h1q6V54W4go%eady_&MF%3$sf97QT6Zrfk8 zWroG#v;-aX?Nssv4m9sO5$O6{j@3M_R2a``w$L`!lXgDzU(q>CB+>c@Y|G4Bk6i6O zatOi6a)#{9z~6*-u5(0>HNCRG@eKYM`ELE;_YeJ#c=%;b;qY`@LTW%VHVsSBj$Mx` z4(az+ScMNtWu?Mun&q$jn~j6Lu6;4)8wElOf@bQVoX9dT0l6w3&y?3@=r^?cU-Rw= z{nObZzgOa49IZ6`F;XXKgGgqckn&MG39~1d%Lh`vGHJsZq%^GX*?e8t&1oS%SB4Y2 z9gpMxAu`zac~-a}Yx6tguaCtT=|@Ngv*S`(z$+k%ZT+1U!(1LetP61CDyam?vE^Jk>gfn1nF;{{pl8Tq8Hag@6BQm|H#@q zF1CYNh4l)7KP(lI92c=azV~dmNXo}nXNLS|SsBg)`Elc za!%C~p*$n>jjJ|#^X_%@VppHf+!7DQFXWHsnJE2J`b#^v?e@7`y*<%Qbd8ZtJ7VEC za+fI-nc#HQcpBff-#ECj>#Ca_BwI}8W=GnW``-=vfgT(o(yLUl7r!bEyj*uLIlGm1 zIx=_gnIy#NM2JQe4L-D7X`G7ACVsx#!tjJETdZ>qfnj8`nN=^s!8)^uJ}$*$DuXJl zgBnpoE!{P&ewjI8g$ZC4N55JNZV!_!X|t*%HA>(~pUBb)hU^l$a}jorbJ6gn&?CzS zKDv#Bf@NAA+@emUcaAH%Z-v4M^cD(k>oZ*m!u@Bj1Oq!n=m?%cTt72c^E89;HRh3K zmn=&u@V&&V=OL@@H%H|iN)bj{HV)w^OK|$|``=e=FkH)>X9hmP8LQ#M3i+gSy4gk#b&WOf9YFk2R!!mTPUzBvoU* z#!<8h!**-#!Pb%E)gfqn0vjjZAd0K@`EAD}9{?k*G$;-E_V-Wj8RQ617#ga5-BW91 zpI5|{vA-SKlE@*Cz@3n?*GPZFTcpSV+*hi*-5uFqyp_!@IxkncBhb+WR)pQ@Zr`}f zEw^z&FAa`#OQH7%&zI!g>(3Iit3LY)aNg+AsgTlAc9kWTRbW-v2;-s3NopJWIfV7c zcxa=UkCaJ<4k$dRU%{|Mv|;$3&kM1YB*%5X&p*ryO+Oe!L9ZNR?Fh%vAVq#fi?M$Q z$E=0Z9ApcrqC5f^3egF(ZNOMPZ1a2!CN@+01hf1mwDOhQnMjI1rNKzE$Cgrir8jMG zB1Y)UywvAFGsP1t431mZXc#IO6KF`8qAYM$g2ArIm%8_JNg9&W)=kM2zs5FHw?$>X zQ-d7bXIYn}B+NCYmGMLfgF1nhXrBDjY)ePtfGI?;^5k2U)*OI>2U=o|J1|xaLTm5Q zqsT@v#wgjZwhUosG6#8@L1YKY76j*iN+*0fM&u0?l*)KSOsPskU#if>%)Qt6#7RhV$$gwH;A;V0~aDrGRJa6*7m5$apO_qu2X{7da-UJ_xe;%DZUJCzpY z@lYAtnfuMcMmS@iMP-SV2ADAwPTr~p#VI6A8aa}pmy>H9&v(sbqH#rD^H%yng>9w&!GQJJ+C>H6`6xN zHVUe~8wzUuG(qM8Dw@(I0dh=&Omc@769|_=LAXmhmf#Y8`+`eI2#M#iL`Ab%@?QFm zOq?Bn++P&>9(Ik);|WTNDN=0-6^&U=ZgneO|4`ZY$L~O7aqfG1;(%fqNZTv{D%vY8 zM8OFeeIE1QFEbou^ClpMcA{)1y6b);i z9E*@p|EsdD|2`hFVoH#oF>WlvN#th+3l$A2i9A^k`~nET1D#UX1uLe06GO{T+FNPf%`{LJ4%2{ao19ofFmgR>_ z+*Y+P#4g8GZPdk)#th(ZDRao@WcL~4JVxjJ4C3=*$~@%BUS6X8O$yDNd6Cc*7s1&) zqebRHBu^HyWs;c}+Jl&6O-lqX@_?zDl0B_Bm)PpF|pZekI4DEL0q9}uRpOF*! z8U@q@AVWK@JNC-TljT1kGqauOdcMT8<+9?~+1#k35?f;}HYUC@0$;)4#z+7MG*A z`H*})dS(Q`YE=`si}`6eshqg4?2F4z;*r2e{hItcx;gV1SiokPJqo}W@c9ce2eVG= z(wSRp15V&S)sFl_gfTYq_Q14L3zgi+*qd%+mZOD(aqZ@zyal^+TrHuz)7s7z>ACGh zU;L{a!*U;A8+=J{Cks;@=KL7%A&!dEsupTF5hTv@EiY!@qQHC+*(fBmdFO->RrG&i zkXXuw@-qItkpcxX-;}PiP~La9#(%4=NE3?FidE=Na4{3lB`qBMk_Fke%!D2Z#k3V~ z8>B;xLh@qht5M|1rhemu^19=pnWL5e)8k_xD|V(O%;N=``HljfIhs6~FAbV`*bK$K zkq1#0q5hU7ukX3i@Xc?p1`(Pt9R;Uk)D^{Lsvd%a5K%AuFtrp^{5Ok}>1icz&9|I~ zH%@=Pz9n3`K=wMDpYCpJ5?M%Z5g~9FJ{TPtbR!KAaKp(K6aely<0>qR+xnz{esNQJ zWSkg>O?d3r<&;8r%w1jrl~0yMdyrb~(jqw7A0g%|`NuIOb12^ot6cxm0A!`!l!w8v6?M&bAIP7=Qlhcz%%Tr4~TUr`>h2|oC{R!EP9e$s!Pu4&Fj2Iw&pVEW} z&rx`0T3+!Mqm<_O6u%OV_8z164e=zKJCm;E)`6llX`fJZ#e+$r)VFCajzDsFEl~-^ z=gdx}MMbr1{zRGN)tH}JD8!B^GXwI$grXtlbluR+M{!bL{9-16&hpmLo`c@Zo7ujm zuv-s{ny17ZcFzy`oVE`ry-tYL=Y6nRuhj`vo@8kurVN?@n8Y&ZrTlJ`Hkifhy7PMD z!l^eD$?a93d8lAB9`JX?N)$flo)o(!jwx>uQ^fIAlpk=V#N@L>`g8 zA7LNfVBmx(-D5(c4Vpnsmo3!LKf7|*8i^g5HZ_b{bKgE>4|k}wJ4N5vZOuBEQA%Lh z*4o#LJ%8jOS<~2b&O7Y4OZ*YN^FWC6i^VsYh}=H6?|+<|yS?Zz6^wOY(2o*jOwp&kApw}!DrgWbLD!<6of zedm?%wfA5xTUQXG1|&Y{7OX~X48S{jBXqEuw4I}g=xq)5wBrjWNE|n+M+scid5al- z-=%c<{?kJKJ$4%4Sx9EVEJ9q(YZult%36Q)cG@jWAv=^t74|GC*R<<6K2D!DZbpVJ z>M{NE9?6BYhBO<)N-izi8e7z!Ks{tA`xM@HPjVrvEnPbF^Ql$ASeZ8bJ*7}TmmxQ0 z%-xq1+OPe7=s&Jb8n52ezA2wluA1k(=TA*Q4v5NPk*iEE7D4wsF+TkdI@zmpM<)B8 zlL7d+G=d_6Cr71stuM~lh;vw_hIPcI9N)orWPT6rm7-X*ZRR=8%Y6nw=w1N(rfFwD zyi%MpUP|{2P|ED+GCs^giyxT25Kzb5I51^CSFZ&cJ#u4J8-zt8X{&wFcMva={h`eA z(cT%}tA)61fYtDyH4#`+pU#{)0ed^{e#QN~s};V(yd5@Q-W{1Y+&FVpNk2G|y+1cb z@8%jIZS}bg79#=g1Lal|#np(Po_*q;Z zn(pqVlMF3}Q<*AaYNIlAQp%aM>iP&gJO?z7RUJ$}$cMlOC=V#^M?-_$uH%GuT{Ydo zn&UnO<8I62*eZV4k|0YOgkSnJ0qodqQDn-ROsOfX;7U7GNztLWvcK+@z6r1*bXn%@ z?g&-MS#K88aFv|QKPHz7{4&`2T)Mz71R4PU^(r;)!qEZ{K^u}CoM;uuumLOiT)U3- z`E2xjX}eap|JIuIcEmR|gBCR{(;4|pfe8;>cLQ7`I<9`u+hUb^Aq_!zrV^YCCd_n- z9+p?;58mCCtSN1jsw>v)VVtLJqMkPy5=WK&b{3Hmb*U0WiLKw>dT9OCa5N(=gS&WP zvJRAjrYN#ozPxK(%1xwNPrHX4wVg_uuI|?Mmw;Jsb36TXom?GUJj8#q_^9J_KP_pT z+KW%YSKsAB2P#(!%q*WkZNF70AdFwx_d%wb1YjW~p1fintWxx%oaR7{SrBk0z-3fU zouHiS11ppp~pvFDtTp6Wqx$?23f33osD7-b@7ZWan8wE6*++lqz(&2g6U`7qvc z3s!!e^~fd90Hu0C+Oa!L!-1a@OnV}F7ltGip7e{+cAJ&W+b2T@xBK@v%7^WIiV!k~ zL6K-`GLl~+EFNZ=s7DqZ@`XHqCtx^jJ5^KkfTYYhbP8ik9MsyIjdxPB0aycrtPmT( zxTS_}T>iPF=c8z~y_rVaK+0qdUZZ%jY4z5iXMi+GQ|5=z3ZlEa9AO?$1#HNyVP)_0 zojuM|@W|BDWPR5Nu;wQ06Q~a=_cl&>kHe%m155|?6Q2IYfqZe~ z+c=|ig!s@92Vd+! zC3itOR*6Ndt!0|y>ZmxL=U1n!@fWYjDu(5Taie8QYEk3F?2 zY9c<^7Cp7?i$5=8nLhc*R%oJf72AN1A4^G7bTrMY(iYuNb8kEt^Mix-eO+*f&j+hdm*1ozFh~3Mjt$)-A2#l_!+peVdg=e&fyvo8P z0s^-NA2DYh#WT0kO6!>I*^{Dm>BI!H_$@W*7X%^<>Wu5852W9%rG~c5Y`@={uhpFA zr^vJy?yqIaeb4Wmm>=s#c#N3#ieu|U4(Zn9{Og+f*>I1Gz2&}F{%Rc2f^1_gKkJT@ zL_Unrdgj;a%K5`yawu!)I72?*M-;)#-ehBo78@d7xBd2&8^)`~UgN|21IkN2tA|_g z8hTW1z5PLMe=J!b@Y(39bgPB7ttc;xgh&hHOj~zq!lSX?+6BkcBRpC4s?m#%(Ve{2 zN_iU2dd1?s6ASI1Rc~RQf#$u?T9!`FXHxS5oma_&_&Zr&ZNy|9A2KfrWtw*Oodgz%*jzsxswi+AbSGC@&>+1!Mjc}<#?zmBH8QBXG#fL@HClC^ z7ROXAZ7m_y|L|8;^yC#iTAQ*fz4-~Y#|azF{jaqZ{)9z^UEkp0vd;mZqK3cSxGt=D zJbGcRz?XXoPq|nLADN17)O7d;Jn1TYJ#wURDlElSL}}`5E?Y;>q~tqCWIAj*dp@Y- zl)BI7zP;IKRQ)tB`%FG?t)OPN*TCv0NI&^*yYHU)4&QtDZHKhBs0>Q?@{R8${#vbf z%#tr?^b#SwD>FY~3q+S+cZ39@jmpxUz-a1^yociA6RNUZ1;IZ*09kvkb-y9CuOO|3 zgTyZLW_I(XUFM)%&t&+S#4tk0=2OkY_+h8q^1~ul)&xh^55@E`MYZ)$RVx=3Rka|@ zSxX?wCEV?rCpGTmCZ@$n{KW+V7Q=j|Nl2TPJ8^-0fPs(b5z_ZaY9wn}7!dHe$5mgp zQ~B;zautF)Baq?Io;*|6U4Ou`1t8cAf-RGDYSi-uzmE}*cni4H(6Nv?kP8$ukff#j zXyeu>@BJ$R;IRXV=XfCI0<9POBS=DFS2C?P69>8AymLN|2QD0- znqZKe=tesKc)yQ_#h4-|{GRPpYe(N^%dgB1STP{sn3;(Rk3r%*Y z<{PcjAdY*{bZa&54j;L(@_*c}*wn7c>t~wl!s6HI} z57~lr*9pO$ke{9TtdD7dCzi`cGl0?BM_W^HW71qD-Cd-j$9;7Dgl%c#r@5aHQq$T- zp`R-{O7*_rvIrGw@3%q0KrpS>&1lum>3+ru+U-{0SW!1Y)0g2<8Mala+$ zxo%DabisbIapTNNHS1ua8P98|S|{P<^0};k=F)POiC2Gm(ElXG5+@5{b6QQ_<&QCA zfborqfMp%ArZT%z=wjNyIQB;McJ0!`T*WJ*UV#<_+fLQ98BMKoS8XIFwJUd-@DA10@HH@lOlcTSIF- z@6dNgp?mjlEbrVUe!M%*ab9XGd+onh^quT(FY|#d-~>0+zD2Ad2+o@ie7P&a$NKn3 zR9osX-`?0>Mnz&ybhNB`$xHUcJ=TG3YS+o-3~z5@WsSnLGeI$-Mf=BsfsLS8$q=X( z`15DT($z_m=;OPj#}O}BB3h99oo%`A`lH8BVEM%!7xY8t=uQ(us-@dqe<{#c1J3pC zp>-M>FCz;k-<>xsS(%hNcrVn^%O=LTb2%3brddT>yBNzLu!Nf*-F$K|Gu^xRakRQ& z2Y8IhDKA}}omf0bue~bC?e*#uOFvKzi2rqT^xo9OM5^MZQS4RIA28?>-oar>>S1cc z(lm7DUYs{&nRYL`jF&o#HH9>-hW*hH&3f3Ch1WbdM-Q!^sQCR!9HC8Hsotm^T&DMx zp?wKqj_MrEm-&Qa}d1a z1Kb${Xy+ByaVHY`Yu)7%^yy;xy5;!ze3?QL)dRGyyEl_M6LfyFf;kEmm4fGG(E4FZ z+Q1H~auDBqfdFHKb6erV$ZnAMKuzSB$351Dh5GQs|2sB*ewNU;61Tlk3uNoEjAI*v zN_fFIm}Hjcjh}geZSKcv$Z9U0ylzDzXNA^lMEVf0XK>_9`M%_zjmsKBhauh)&pUr%+-)JtZ0w|)dw*vZ(}4a&>z%zuO7lL7_4Z=M9hl8 zRWp$sZ8)j{2gJ21?48EYD=r4{w;YNS-Q-}((4_bZ`gihj5uK^@g-3ae_bS)&v~Mc9 zJtMxsy1rhxRmBc>uLuEuadPd@O$6kPRIG4vU2FQ=je;H81{6MuYDBPp5RgR)6;cuu z?vB1tC(NFG`4=X)%8vAUdZ|X_Its`>eLlMwlMR0LA0YWwg88tQWWc@@I(R&?^FLe1;2%)e2Zxos7f1$|S z|Cu5)m!*LZaXQ#_L3N)Bhb@N-b!J1u*}*C!7jzCLJ`v}n3-7STS=0?aYHI9v^#lhV zOtbI+%cpNlX)MvYC0HGe8A%}LmWrD=IL7+TnLUPt*@mH;+9WmkA8EqH44RGZcE68L z`2BGW42oI{k&5_0r5r@3Y%3U&qAD@8n75_cP}>65gzvVbE7FKn_#h0FGhvNHvf`1F zB^QGwuMk)m)7o@TfDSEBw|2cW;$=UpHf`etaw`BjdYp-&v>Y7*i+f_x-XJkf{`JJPsQCshkO#VRi5G;p`HD55^a& zxA4zh0l2H$H-?E?Cxp%h6cRH(PjrleDfB8)&tc}@YBtV7r^VXkQ>hVLG4$6*L|;3G zw+zYIPPnQbsD7eT|9FW|>{dhq~{|qOE8%-;GooyEm1mQjd~}l^yY2 z!i>}rv*5Bhn@08r&x&g{3yOE^>F<=)yc^5k`CY28pVpNS`5~;eIaG$t328YhyJV&} zvG$$VYNlMo&Zl@Cn6wF9m<&@vcYJUC@1DeytX_XKp}%5zuDKnAlf@Ab-1DM8Z8pH4 zeWkJ{?ck{Tt{FBRe<7;o(c??8$9MHXGxP5ZHIVlWAJM{?KR9L?&^u+)n4qQsluzFu-j*i^ zNz8@^UCb6D5$~Sh!O1MCU!jqaqs&V#8?$*K^8lG@3;4{Pg#4x(j@zywDwqtmJGgQA z`|K}kir2pt57%XvxFglPojt@$_Fy3nG~+iGWcdZ$^xf0KhJYBP3L*AD5e{A;oLhEu zFo{Gqh5fNRWKTDR|IzVbZOqT#JKQ2@^5Gyk2w<)trKWRjo|xpR$A`o|hrMMziciT6 z6+UhRm>{`w>^_dl{|ze(Ql8~U$3m$NnN^6pq#F>5vBHm6NCGVT-OLg+;D9luRX`DI zCzPou96x1-MW+RPnh*zhx<^L1Nw%vT`+}I~zog3G=qVF|)lr9zNYQ_=vKpNa5SMAJ zcDw1hbG^eSJZIa62nVW!&Q6&KFaB0G^b_Xko4I~HCMM@2-hvYH=>Qnw5BYoBBF__+ z_c07(rDyA8@-(r~w^xz~LZ*8;7_&!ug3De!A&+WIQXipy4r$}cDt;2$e7wR$>#hXIaF$0Gcd4q~{ zgLm3#!8M7_xWa)pB>LyhG53u_;<-cH;oDm6jy9r;q zbraV81-qdP3jYGTiedi?*o}z#f*XPL7wm>Iy!!|2@++|eko*4vyAe@VQOSS7?mu_% zA7B?*+izKQa2#;;o{v{@zF# zp|J(ad7D)fIf4|AsGIQMc}FNiPuYs%L+myV0lw8M>`(?lY&1oi^2qn(Jn?yk^yG|c zPICN8!i~rrsh@Te%2(9wI}+rebQ4Z_@G|6}cN4Y{Dw4_bD_LN66K;B7w2~^KVZUWw zYuyRKKQLdOGxEohj4pYJBTeA!f+bkAm$4v{$6$!VYGbQtZDHc1sh=g-4B~mgkLE5_@*+=s)$?9EJ(L+FUl0xUf`jd=4(ileo2+OuN0G^4vMlb$q%I z_>_aj*2ut*rI-NYaZQweM$unf2s!qy^zxf)e^i$>=&?V&y#5`0A>=!{$Oam-6%f4< zoECa?DZLT>3)c9=W{7E`KI~!creo6CcWz$dUuIy^+aqQHTzdeAg=a#qSkDsh;6Fj- zAw6d@{b>XcN?iZz$d9B3UFOQAI?;phP7qNQQwS2SKucWXP}aBakb5Ixm*=91;#&(odUn{ zs6|mA`OaeBlx67H_g60D7cpr#3_gJiY+b}QfVmMdd|0yfDaj2)dyUhbe-n z3JYw3BN&dcQNf3tkn&KV88UpQwrjtVW%L|WRhRu=P4VE?qKbOW67~szRiFyWnbJQo zh$9Z>9s{)?cJ{i0#rHj@z<@g#we6!fK@TraPpA=t;-At)L6eL7Zi7};C4lDk;-DV3 zP$Kz&ql7M5kCP|@FczzA!qeE#+He?V575|W@1zc&$4`NOtQ{bV`wKuo^VIw&)oM|1 zhZyHUyue{0$3P+T58*HhA3fA8nZD%ypHxr6Q*DS_;~t@sj5RuNUW-aY;+@8x2hI;c z5R}$g;2hn>@BRs#pVIstI6u`s3!KAY7lCtR!Fk{ub@M!MzJ1JfBNG3sc&k$9WhxpZ z-_oh~)u6NII5PBUQ6LMD1r$IxdPt;w)7EhVuEp%{L_BQNL-fNB4k|Zc7Z#;8#k6dTy<18-XkN~6XwG>El9-wR{|^~xz||!}5u9pAAoq$7`I*%paiO~*`uz4jbBImF z^fobGPYYWM0$jUOZa_$%vVf0PShc8L;O>b7{6J@10aE%PIg=WOK1!4jf_lg5#|wO} zCIL=wnMjWWS>S2Y42{7UOY?a5Sf-d%x4vQA+fxi%NA`mpwvNvZVNn1}u;GMoB$FEz zlDL@J1RuQv551Y%Gh;O2Yi5JoN?RQ~xc@A247eUh*?nogItX+ z3R|cC#n~K=q%<|Fi1yL?6=U)U;oe*jx{i7)5WN_uXL%@4RkS2d)gDmu*b$D7>=v*? z0b1ZjQ$MHvLGB7L7Xcj4w#%pf87vn9(vCkR2TCa)*ff)-ev@GY9Y}BiTAc~}Q3NNt zQEbg@Eq+7K;o7x{rWNQW<6Rl-1*#L{W^D!%HJaI;9!aCyr+E4T9+S%w{9){$mrr|X zYRxy&)Gtoo%s>1fr*f>uu#aDaH?QzP?$%ijurxKfaroQZuR}n?uyrzYk($cYTsAQk zz^{rMR|yugOVF=9(jVl4T0kWykXsS5e670oQ(1zqOareeJk4y*sQ0X+^3uqyDTXI{ zALORC8G&_znG+r%WY7fyJ+KW^CNm~K+6!njFxdj?4zsv;cBe2V=P5U6p)wA)e*L&% zckaE$n33wWU1U@IqmZad7V$?as|9s^I6n8+e-U?ilR5*sO|3|4C zz@-8jeYg$uANoYdsb*Spu{~&v$$!Gmx=lY#`-2>9v}-`Jo8ENlrH~uIiEwza1mD`z zAWokrFCoCz!G1*yXdvq;nQ^!gOEa6I5Vgd5svz(wzVmuUG4PY-GdU`6NhE_j$(xiZvL2pk{Y$e$bve-(1%pw9<0?%c(cYG!@^bS1q=3Hiq zY!LQynaKh&qd1ot#XmBWMaxX-kIX2}WyXFXGoX%tWESyTX0m|HlmMAU{3A2jbD7Eh zOJ+bRXELMsEi)xRW)XnQ(2ZhK{99%~lh0(PgqE2iATz}anaN(rEb!klqX1+^aV|62 zbD2e;WhQ$eGYWF{3z-3D@VCrl|0OfobD6RKmRaDr%oP8~?9G|XWC5Ao{w*`0K%no@ zGGqTEvxvWB#(pj{_WzyCWPi&{=|W}{|Hus8Xh3G{$h z`>6MQF>Eba4LNKrKxSV8Qi9H9MtnY+p?Bb+H|H|TVT0T_lbHs3G^09~8Py+|X`GE_ zT7P6lbuP0f=Q2ar@sG^DT*yoVJ(_6&GW+sJW*XBMRK;NTf_T-PuzWgP# zC+9MI^1qRp#-Gtl>q2H!e`I#nXh3F9$fbc3`7N_2XqlnA4Un0}`Do^HAu|nNG)oEk zM`l`RnSDXa>n+XBk)+dXUs-@BdTwlQPf!#3i%u}k{9{R72 z6wswShb05mg@+=Z+pGW$js{9mPC9GUR1iDEON&|%jwA32hHkQk-b0`zn;AY+K{?;2 zf}Snob^z^TMflh1=0k2icpK&oJ59s@izER_y4paJq&BdUL^NM5)$MgLt0B(sY*Ea$ zRNvOr-+s+I50kOU2Cr?%5~ISEK;$ zs+W8rHy`<65n`211E&q-##y6h+JOE$JCP@6P62&eIp8@hKVT}z zKG2|+>cx`{R*#K0zW$f_zhKu=#bHBk7X6YD3W-tZz5%EsLx|-&Tl{mNjiFvsK`i~x zc1!#_J*R@q(bEr*sUSUIIsrJMzx-zY$>sbKCpe0n>}p5}B>lmFm$-Dfys*>us>xJXoI-RpM?B}$Hkj55ri zud~fMj+5u?RNOi#-IG3Fnr(8P@b4z?ZPaZ1Fom~huyFEC;pM?y#AD__eG&tAo1vqx z*D}_N)n10NmxX>T+dKI+pnd7Is5CUxqtOIrh3n}G9o267dMyXlsm5%WHLAcVwqA35 zIo?G^!CE_hc|Ryy`0L?hC6bxut-E*4=H?B-WGTDoWS+sr!P==@($^EQJEXVwY)bn4 z}xLjGU~dRXh;#KWLG4WwT%x^<*^NAvjkOV^-@5$)iExmDM5BS?XtDUQ0LwKsr;Rhu&AnRQNYX zR|Ou)2mZ15<_lKT3H3|wX_;BDe5Z+6w)ohu~nAFvZoxZ2tmX!}|z_zh`fMt8m)gZsRnOdu( z<86Q0p1MX-L(H`mXwWbSf7(?0^1{oPzx)eu`=<=2)mA@iu(eB6#-$slG zVvruO+huYvU-WU4yHIW|1u*f9ye!J5dZKGk%7OU9e`vl5u7*ABi$k-eBc zkg>>Ll$u$%_Vq{d5Q5smgLBXAVLP(3^7f9{=SAJG2*zXVj`EpZ-5m){&QM}_bnw7z zQ9*%r=tp?NR#y3-)Vs?EpL+Q3SRN?ebRGGaxRlT* zuZ0r8O)Z)HbnnxR?WL{J(|!Bv%ZzgG+%T~x6EEM^;i7>R5cy6q^zcWBU_)YEBzR|0 zM`Bcxd3?Tx@29b2)JrNas|=}E(Ihj#13efK80(eHkWIBgf#Y0Or_diHb%hN_Vvz>P zf=NysMMzasL=W_cY)ik(Qs&2DZ>Tz)6yjn~yUSxwjK~%_H1m9(ekbJc4TTxUxG8zJKq6ra`0(oCtL@2`+mmcFpt{4@!lLJw2_DgI>U27N z`vfIe5blVc8|Vlk$jD-GIfrG3BGrCak-9VBOWa%M)TbA-2_%<{D;7#Pdt+koL^;Q5oY|8IeRD`$>Q9uYR_ZjvQ9}k@n3afYS5c=#DC<>0n3NgR zI$L)#c~))LL->njuBSw%>Zo`^Qg4;>ZJ3h(Y9c~a)JOhkQ*+aJKzNz#s$J%Cqz$_> z=g;HIUgck5uU0<`Al4n*!R0eYt~xISBw9&O!i#V*6!4I;CkES76{T)%{zR!ndTi+) z9NJL%4JwrF=UxUCYO0e=nEW6{dMu7vbtF^R1r$DHb4>m9O;2IhwG#)wlyUhuBx{Mc z1*_I%B<;R)$z&(ln%z55v6nBB3wBmWe@cd30!e9N4lRD0+B9$SDo=l3KXNshExl|S z|3&iN)!Ug(Y)dM44uA5uN4D=q)bgwESFjQNM6$wToL|$L67Jul&UzEnl1%UC`~@_L zD%w*OJhaLnr!?)(v=Kebg%iQGUYLYE%rCtw=)-fjP353&)M|7PqQKv;Jy^Cr;tz`X zBKADj`$vjW(^kA4m851Zno+ceEDN7@gHEEcU5C(m59OUYwkI~bS5>?T6C)$@Oq2}AK zPFa;sq*o%eSq03K^{M*dh~7T$Wv~dJ*PXp~iRXxw;ohjzFWjk8-^Wa*HiKehw)1l& ze^Jf5GKfZ$r+zkal+k^+p$@nrzuc3f-w8h&OPAZ57%2;7*ugz=C|S3d@^97I&-2V* zjg2_8xqi6Xb3?FtSPf*A#reRq0dyU^z;bG8tG`-kD$g}lI}FI=7Zr2Qi-V491M7a9 zW~Yz6^AMAAsTgppUTOC7T3Zy^Hy zOl|+>1EUa6P9uBuS8cwXJjl{FE}6&F$Q?Kaf6Rb~Ot$ueLhNUJc|5leJRe**@M6X; zCx7$L*{w6PeBP#mahX1*IkxuXr}AoBT@Up2E1N=Wm+wI|o?iK$hHsc~FIP}NgaT+O z3e_*s1`OPwmK+UBgFkQ32L4XzMQMcr@Kbe~Rv}K*%PT+K_NuQDqww>Z`?JxVm}K3u zPdVld^MxghqlvfGUpfz%JhqM|KBgkZzT;I#%I%Kh8QpS4O%uovvs|awik`#Q zZ_)E)=Z4m7ez}g)RG2hd>Y+cmBOC|fqkO8#ZyAO5Wy?$xU+K)YW%4DAI+c=*Tf03+ z8th)(cDe9Ihaz@X?W@Qu&dvQxF%Buv|_HIr80#kMhV_ekz5%_Z98senb!}z36v1PbN|C zI2a{;d-jBY%eo4rS@-rZCG>#KuORsGRo_894zj+dn%^H~_Ak3yr`huOT115rR$klE zl9G;d^WJeZN)Vr<0!kCT4zqnbcVxUT-iCOnCoG7oq&AQyz%}lXcjvh53oS7#z4D!U z>7R7^mug$W-BA7g)cRP{63>q&!b9ZAmk-V52Sv zDw!C*kX);{(}lfrrDZr|_p{zkjkpBG@@08KmV4J9MM_G)CHGN(|7$BVB!;ZtlRo_p z59f2~lJ+z$a{EZR;(!5x-t56;r`||bVS$S4kh_8v@68EwqCdr#UC$Wh86lTB5UAk0 zG_BI9CeIMC5zD*wtw#|18X4R`67sD+R%Q;- zwN(FWOL**J_!P_7;RCKWApCjaDQs%Agol15LU+C}QdrPGryqGO_Iy;_^?}~l^tqoWy)DOW1$cY#Y9H$$wv(%yPdf^pu&X zw$8!LlJ?C% zT4J^^lGOT|ppO|*pM+ip_3u3javVN5G&+jl1-2$(ry$M<)%6aV{MtRUDGzY3V+yYl`VK6gy zJ}8uy-AUW*^7;{{IGFpny)lzL`|@I0Gpel_b(3rJaLDW3WoFK|1$W|NyV| zoIJ1COrn+8yKnYt~0-{hAxR7BRK`B>P}rUER6y>Y5)Lp$x>fIS2D*79?O-+#2SB-o76Cr^Ppien6@1-y!FTI` zu-3vFnQ5^{12{BVY$AAXZzttwmG3@OsuSAK`|ZTzwj*&9+XM(4KIe{KH`}`-aw<}f zx;_I=I)v4=E&`*5jmDvBYg0KFGbr2Dz)+MUGBozB=-p>`t7VaktYhlnggR}deH;xd z@+ST@1%sBBTu#QEO;hD2@0{}7T{n@r#=JG*+N;T(#oI?D!*Kslx6|9c;Ft3|mG)i9 z4>Vv*U3WgBFJ4#5t?QR2Eah*tWwV(7Z@_(R_ ztht$>LfwkMB>%kqo{EN?I^0M7mltRU3@)FcBU~}?o)q>PI#N{bG zPaF%LzeHfg*5YYnqJ!i}yLG6&813b_m-y|KBIw{O$BdXXx z)@;TcVZOj=wrR!ds_&r7fFYAG|JBLrk8`>;CDDgXpBzrL&y3F}K3@?_waDSuh(~`9!i`U9hDp)omdL;=V4`E&EeMZPT zKNY(^z3+hy%+o{(_9btcxzD%Fw+DT5EPJ(?_HsQjvd&)#5B)1OUm7UI7<@d|K=<$kX7%O{LNW<^ABf9 zOzYpACB1)fmX1G64vlB(zT9@K3{UzoSJZUf@#f(wkdS2%rtoWXbad>A_Gpg=Xnf*R z?x*k1D%@WBQq{?@mNZIey$_Ei5enA)uqpXPySwc27_huUX{x7kv% zFL5&%RyOV;)>H3B9lO@(?(rVlF*pC9L2AJz0b-VGFKZb!Qp+|w?(~{ib?2|jezgMO zzVhK^;w+@BjMEcJ+R0xT45!<~Uhfv4+7;rc%wdiYQj9RN=|L@h=Em>B zY-kR3=d2Y6XX1mlUul<@9M=x^7SUDO_3=4sMq-kF+e-iE)? zn#+(1xKT^W5&=B(ABzDFb7FTqk%&6de5B;e;9%2$aqOICKbdRX2m1UJXBLH z2&Lu;yQ&_)Y`3=iSl*?qpy2A7N6UVKKA+y~r9yjcAkWW!{p5Be$|o#&IN6*+;Ktlh zUSC*2*)H9`GG=x7=3T~GpB`@041!Z6)LquL-XG|N@eIbIvfFn-_8iG?{z;qdxsOhp z{TZ5}*`-_?E|8K>wf{86%j^3~MnW`ZsR@g!Ti_VLEb$ayV3zv+6K1Ii=Nz+ir-`=b zv?gM`5{+4U`UkW0wHJ+9()G1u3iPk9&!cWYeBm`3IJw_T<+*=I)GX7MPrsU0nE~E#dFM(4jQur4Eg_Hmcr!# z%+jO4WHSJ>lpcI2c>rLRO#Wb&yv{I7SAJubPWJwdS-KYWpD{}wU6Q0+a2f7B(|~MwH--Y3~Hd*yck3Ll0Juk5_BS?x`A6bsinL?qB@wbb`*lb-N`#rWtvwBD#!fA3iwG#5E7E+pt8gJS9u zeI|K!#z-_?gE(^VzO5&TACOsm|FVqxv-Y?A zq=7nEwbNX*6GXMsx9J?7vP#`D!8jxpp#Lj3wiD~UGCDKXJNWJwrxjrPpiAm|4lQ~z zKFpA1eD@OEKSqqjz&d+ZmT@Y;qawOamupOA+R7QKk_xWnTftwMCHw@7!fK=@C>Ib5 z66v;NkSzyq(j4b>DrisK&al5D>nQ2S#W`Mk(=NWTP``nB`hXO3hsR!cTw6(?RHj7L zWKLzYOibsJ>HR!{npH>X3J8o)c@lnE=j~y^Ey&PCewKSvN4NIG#tK16QogwNwl%?~ z*W69PJK)=4Ok&#(pQ)&6VPSQy-1Q0--v<)rdR}A)p_U$DI#&qO&W3AeuT8YhYe53@ zU4>d0|5!VN=&A_!jyfM z)8?8A@*o1%BSYiK4!C^79v}KdM1sEgfbu%;D`^Y#G~w7?e+)s=Bsq3Q@uTR91VzF7 z+L1HcgIzN}-Ncoq9|vwTfATqf0_4dyZjOH~chp{*=j={aTR!N!UgAAxJb7J22Y2t~ zlZ{l#kQ-+}3QaBYCoSf**i&!g^LM-Pf@zlH@hW08-(kP^J$$^lNB%ngJQ+5?{!_Ai z0iwVx1CdR(yxK?G!4i+Z@OekEn6860zR8@o$4y?CZ29m3`h7Cm19+EA1wANfPWw%| z(76+BE|eaP1~oB)R#P)Lf7!5%D3%2Fh^Fb%zVcv%MVm*;B^a6COc#+p&6Ip8Hd z%xNh-M3P#X*=#q^XZKC8P1CoWy@dK}Kq)@U`^y?coddJxYepDPdJD+W&@cD zu2L7EgG~iLJBV$XD+r6@J!G5814H;`3cIpx0zk=A!5==|JaHg^;j4dzFE-@bmxmgSyJe2fk9*}itj79)Z6&^&A^7!Rt5MhF58 zgYfn8oT4XqgddIrzp^`$`0hL&5rpZU0A12B2XOpqX_V4v=6O;y;BrB>hA(QU-NLI- zjUTMiSx!H|iEjXHutkt0shz>}SkVOnJ%9jR2Tt$AMb@aG=n$y8N$6sH#fJvO=oZWo zKoy1mCszR1AsfDj#4$Q!jxX&zV=nl2?mo~i2;U<&1RgX1eIfzpne>FvfN9WKxAFGe zJY)-Lm8kEM%7=Gj$VUTz5P2Ui7vqxS*RzfH90$S&DY*=2;PFj7X!C8LJ2U8mllHsm zfKRGut&_Qh1KK6W2F<+#cBz?7y`2g+Hq+m}A25`{&~WW_6lR~d3dv8uT@}H~%i+Pucn>$WJaeYk!*(NP3M&!qRnrDP=4Qm&VLr zx0tcI^;4Od+3~if65<)b!eBrkdu7nJXzUtqdKD35isi{%vMi$39{I?gXC4OGo=nbv z&nB^iE0J4A3Rn1XM2I)yaIN?Se@;0MuOnBaSjZd+>yJ-1_k6XV-_?_@y4D*lXTE9C zsMzJ!uhtcRAk|{_@i2sQLF6%3W7ix$vxhddkuIO$34Jb8hr=-BVDIimTj%Y~Vlz^$ z>Y0OBhJ$P=#nIQysct&BO8)x{+8_7Bw&q7OC&l?i-A_BPGS`u2X4!j9o=^5aZuiB= z=zUBTMKZPY7Awcmb0H_t;Y=IWgz!F)(5uEm_&mX?UVL2jgvL=^hvd{{@0q(*xV})n zO?4}o=^pr>du6m_z3GUR z6!(d~`#(Yuv5ZY7bY0osF)&BI*`GDp*5m5(B`<5SdokhR%xgcw+U2{g_lzFaVwWmT z(`xtPO_wj7%SNAj=7w0`uS6A^s27>nvx(~FX{2B6P7AKmxCw5WxQdRr_8L3TeyQ}Q z8|pJ1O(uoK`Ao{oKeC#{U7nD??2Wh!sgkMjVW^)-r~jqs!!g6)~^871}~pW%X*})fF|) zq|fz&l#;YII-%t^u;j_u)FyYT-Zj)|#@pyFPsV&L=h2;HD3e@WZsI=4~CenY^7G;HU{g39ie29 zUa^>VR-sg4A+Bt>~gs=May)+t5rW~zgX*n0KWm*Xe>GrUm9{EMlcDM@|z-7XI$-n&wC zE6+6!3{BcBL?y3AR6VF3whF}Y>H{bu`C z*;UO0t@cx8JmE?Ke$wsx?uRZK#2Y^eqNX6 z8sNqe*S2D$ukgiI11ygvaVH?03I8_VzFW-ce~FP#lKY&;$Q>`Mp2x@uG})0^70qJW zr2xpGS7pK8S~QUT-oh7`k6!wM+CmpI%xX{jH!LQO| zB(Td!#$^l@g7d(%*meTXa@)9*R=LlJ4y@wLnTvTxg3d$NrjXq%Gn=HfZoP(h3z3Bi z-FdILd|*WuXq*EVpNq%xpULILp?r?h@OoF`k9JR*1?eG(&-m<)_pq^OTWJ;cLtZ$9 z;@Vj&j|OAi>lB-Rt?Yr(bQTTf6gzxFlFJC8!ut4M=a#(>P05+Fx>7Cd;?$5ZoL?R_ zf+=4ZH^{4!^Oq6g9bu?dgxna)F=j)j-Kpnb5m?u`I=g;Y?_XLJ_}?HFfu)t$%gLx&X|Y+8E? z=%hyM9)?bsB7vFgJY4skF2jMgQLZjsuByasz^-H2(F53ZT&(YL-n2_H9+N(pP@2|f zHxg@UMsNy#blT)da^!Rawl9X?XhkMw@ ziCx7a9~bf=0V=4DIU;f;5PNa}87;E~?0%v>I zv#!EVm-|qYkdzBH=UlHxGqT}00p^_oL(ugdaO6N2c`6hRB)D9;u=nWppe@F!pTH?_ z?HKW@{k!wCu^LOUMIY5p9_?H4Hywd9j!y$OT>TGEJ6dy-DjT462l`kw$B8$0Pim0z zai?Mi)W}JzubUBf@04wgt6fYka~x4`>rQg>JgGn2Oq-wTO5W2KDEZQNv_X5+Y3gpK z;=^G&x>_`PBsu(gwG?`jcWZU8pknTA$7Xoy(Sgt?$*S0#H?Nr6(G39SKUC}8?xw!i zuMi8p0fUkyl_3&u@462Jl5}n|&kQt|? zOj@gqV_pa|q$|aucCt1U4KJr#i_krrg(hm~G&_2CJ1gvV-}<-B`4XeaXMz&c^PPl41LhG(57-KYiKQb()M@- zL_RwhodsUoLNsu}@&)Go{+fg?zl>a8;(&+Rbh;g`Yyv5xGP5J1VafWNmxYfJpfweV z^?Z*`wI;Vb;k~P6JJq!+QZufyyh7iZ%Z<`sHT7hiv~X;?huh&dg}m9~y5a#n0$Db% zSK}bp7oNh)5_VpVc34eW38(&u=-_}=FPysRxsOr+>kPfO#|bqXosFoZLhd;+uopBL zd=TZ`CFvUyv&2XT z0i+&{MpB^XiP3H-So;8MumFXgL_WnmIdUC78YMbyU&C|VvA_<8jn7Ff&Xlj)4G&M3 z7nfTtO2U#mvw5;lrZ#_&eq6S40GuikyA<}#yL~!hC#*ZC9{I>o2L(iI+lg(6J}H`2 zvf|G(IWTJhMknQ_lCmm1I;;f{`^clCp`%bpIW%^3@8l!5)CQIkDq|0I?{IYzA09ia zBdZhT-HJ3bUzNbg}#aMM{}uIZ}tTjPkJ)|3JTsUKt9t z0)4ug5N4DA1eh;gP)1W0oanF_Lz4w5^i8oC^GaK3MTouwIzOb0Csq850_kf67V!EC0DXj z$G~Lr1!h4RJRk##m_<zKqy&Kse?y}ZA zn)&pZAySfHGn0&YdFHuIgo1zKjnnjp_;bq>h9L?DiAAnAW3}-YykOdqRWI0a7t=W) z&gMaAlh=6G^SAtv(@w&~ghy>i4F2 zV3C6`p-ftCM`@>wub2ml{?}3p62Hq=j@Kwuro6u+{r&wD9ghW=&-y%;_#^vKz#8@| zR`J&RV{XeV_$30}E1;)4>#4Q{6$V)a*fG7iW|5gmR!aZRCYkdwq?8_8 z`={8?`)!jvI^_3d&a##ARfsyRqxtdk3X`g-<1{q=ddcX3Sm1I;1k{Mcy&*>3?`c)a zm_k$%CSKqFw-Ep0u)wC@Q_NQ_k$+DycXTTYN4bguT-w-fl88w6*g0dyU%{F`{Ezs* zoJ~F%LQ*nQmF0g1;f=AiF(qo0h^piT2+?bEz;Y)|EBn} z$Jd(QO&5{fq3yUH-?em^jS;e)1cn`b`M3G95v5{0k1XfvurM19((Ur0&U)2 zcSp#8t2tJWe41*=0QoqO*SmU{C!26< zM=OGJxY5{tn26tVx>x$Hfg@*3{{uM<+;neQ0Je}$N+a!0x9o6=*F!F^AYuRiXNJEr zz9j>kXru~Lsz0wz=yCH>EgAl|eU|SewUp%IHYflC0wjZ8dTSQ;)n!5J3*bn}BI?#v9KGB2Aegx|wD8$M8pnZPs5Y7h(pk-}n zo{<7v7F#_7O%(zfR!g}|{rgS@{2C$VUx(mckDJW%0J`MmHgMaS3S{pAqLmt4PHb}b zfM6tHlHvr|sJTp&49s;1II_jbzq{e8W}iE<(FKx=hoc?Y>OQ|6*_?I%II`7c&m7t6 z8ULdr`-*rtHZ17`0L#=;_EgJO76BK9OZ@&c>p~a-8M+Q`s2jGrEc!%bFA)0b_3(nT zZUYed;^b(A{vzWALSH;wiN%TS4?@3|Qu;X-(9r-wUww`iFA48ALO&_s9HF1oj7I1~ zIJZTCF9C%9xD(s(t61?GR`>YlqVx_zB{(nnq?r6M{O}9|F#K#iE)kK}ke#suY@E)x z`DZ)MHs{=Yepw8eGj6^iz|Hq$J>%wkvi{-b8=|@S7=FLG`JSw2+Z+OnlH~bejA6*KXoA1eb!Oh3;%dx*i1aR}wjl$;ti<@6V zc8Lhh&ByRVbMyHDOBnw-H{bArn_olrFK)gk>kVE{)-!It;W;<|5)r`7H@x8Hd$MBE zo^$ijXYdy{A1LlGZoc6;Hy=ah!v0_L;oSbu|A(8u=L&H14FPU`WX1(IA6+1xA)1>H zoZbaD{}RzZ+32Vqy@P7 z=x#%E^9|3q`B?8SxcPf!b8m6SIz4~*jp&|e z;`h$4@_3Z2TC2c7BtsU?;|<2uX6rYgIFWDGE}beuM>5!fNX8Tp$)H0=GMKAd$I4JO zv0-mpoA$^~M;>3k#MSyThO-KHa>n*fU4iVOy{@#|Ll!5>#_|r2Bv;(t>gf8WO}Pna z<0iuevGv8&L^xxm!7neP%}8R)lMQvt6P;@kt|mUG>N~4C4$W`?JS{&z_*fyoXy%$8 zf;()^U;62oE3Lz?cc#pY`TB*kxV+AqZ@UXjl8_vHgKPb~QbYT;)h?#~bM8O+CfBox z`XdkS_b0Q1J``!R@+sC5#r5|6*jp2276og?F#5h*=^=Q;YhNy8no|#ViPH|Ryo_yE zW7;w*!d;NJI;1^17WfsLBF)}3=jjWBLivoi%{81%ATdLtnBH@~csE%}7KY5*TinQk$ zU5jzNaAzR05TeN^m6`bJL7gi>)z2M^pM!Tw?v(U9l$7O73_glYt_nyB4ys7Ut!*Gk z%9|P{Na9(f5!bCPZm6>1kob-$Xb(Rr?;5zQTJi*# zfx+o|nn{?%vz&|BRF07>eUZgSm}JFzcKq2S2*iwG`kX6LTzbY8Ny_^dSHx1{4_9RP zwMT97-rro2B%Z&xB4)Y0K!q)o=yTiqn=6u(2XI9!v3ek878hKR;fmi}5lf{ru1N8_ z3$BRe`(vOGfGc82`QN!BNml>jiWFxefj&6ria3=)%yiGVBIrJL_=hW!#Pj#Sl4Nzx z6|sywUywt5^n76Hy8EBFBI3FMQl2Tfo}IntOLLrGJ}dOn9A*zMhM3yo`G1z?Fv~q# zn!{}Le_Wd5Pr3-D_$U(`eLpMiKfj+@a{sxX0iDPL+|Rn6!YzRgxwxNo4FLDEVU8A% zR50nz7P~LR?50!Md1?f!N1eT||KY3NXh0)C%+xaWPt4RT7>Jp6Jq2Q>N$a=C#C3ni zOvQE2W2WMxXED>R{dd5ZYGx^;Tb4re8*OUa(2_s@DxYG507_u&3QYDDn z_9J6im}Idh*|s_;CYKIrC{9f>h(fC0Mbq{Ahi?US9&<1<|GVmX;s!`2E2&Pvx`4EFwwewZ%efhJm(5o613RnDI z)u?8|5b6G|!;vYqX5!-!&c1vL)er*}%@D)G34WM|SGDFcrmM`+7nYA%6hjP~L!5=e z^-|=?0Ref%;1{(Pzaw*7HhLXz+&xH?X@Ab{QWGei}a$u(H3h%o8K1e zLg9Zb)`m7`7HdPB|Jh>gRsGvyJ;ZQsvEKLlZLvOUG+?ntUr*pfE-coDHh{$%-EDxy z+N=7^Vtvc;++yuj9fytSTKvaiJ;VT5tP6z!i?yLms8`LzZ;N%!#JR<~X5!3ZT_{}O zT{D5USQEHM`o`^uOF|uAM*C_!H$X-UbfOt_%lbqGf8m^JlG%4rru(eT<^S8 zudU>AS-SAO>75)NNlKQFcvA;N*C$?FF?ca9SpTLgnrtf5@LpYo!s7aCwmIgOAo|T7 zX4`yLv90Temm?m_S2^}o5B5kH#f97^`*bPzv|zo z;>^#If`T;r#eBb~F<<&5_iesfUw{=DIrv9fFVnBv{Z{v2*_VHrZHRHs_cJofuL zq~p?37lGhORH8NGY~qTO15yiuP_13q65`$ofFBxhjq=KaLAca3u4n z%qD1+w1Ra@98eR%J|lv^)_?@*s{@53*K3hSn7m((7xuj0-U8OV+~ejdFV9VBC*k*U zwVs;c^>;-t%oHs)_R`@Oew6@XC9aox@dW-yoV$zM4yE6m^UeS$mm<>XNGO znm5DOUrBNibG^aeNyl=CsQT*fBG}tCq9n<#<%xJCUWiBk2%DG>vfGrQN%Gd+l3w*m zhk1y9Be|Aty<~Uh4Fym5&kqO{D_GriR}8L5Z3*XoV1seCb?(PP|E%^@H`trg>e>&g zTJfq{X~{JhtX?r$(o!R}-@pGU!@wkE*ER75kBCW#3xYcuiqhN+XWn+FVdn3k6c58rvt7{HeuFSKcIGyHhaHeCO(of?>k} z9et?Nq_~F0Z2s|rmyW-498X~@(TVRnW7g$b)MSZU*+(au@UDp>w=$o#k1cLxQS)@0 z2*-#YA;vuAy^{wSqb5IB50#5tweo*#a?<5;7YK)c6=JvG*=g#iFZOa8pzM;*+N9oI zw~Co~QJ=jkb>wzy*iNi?{b|IH=$0z)jmg%-vd2st?enSQHx?`34i>gMbW5P4rMzYpwRUp2~-?T6z3q%}WWyMpq0fhUR_XODc%TlqCm{&>#qr&NPF zy=nFcav|dd?S^eiIMajpz+Jn{rmn<2=0U;cgh~FlYSFdHiPYLQxCz@l#z|}T$zb>S zJJ@M$3;=9ldEMcNABey?AY$6o#K0Jzp=K!~Hy>5Fes2LS&X#wC5dKKIX*A{>9mjM; z!jI`T9*7_EbKJjQym(j23uQTa%O3GF#xfm`x?3c^(&9k)$>u97iyEWbx?gm4=>e4Z z)DP9Y{!**T{TKLJgJs&4gzY9iI6BO4yA=i9wPhuE=w}1VjW->x6TP1{I&pxm^6t#6GvJE@a9w>VXFP%mx+dE0`qlHo(3TI2iMW)vB@ zKwR+@i`PRp`QfJjqm}B3DeYve^xdJOh!v$9$)iFz$WrSYhc0(^@Y$7YggO$KDOU*) z*$GI}k!8Vld&1G1bYam%HI9+s<#CU7&e@7vRT!lSu-VtoU`O>>)teTxA3g6`vH z#dc&p-%brkYJp)nF8Rmyi?+S~Fot$}hS4pa1n?#H&CK*QX%Rx!3h^|BHjRR#r%_0T z>bDQlH*}TS%)*sRS0MFr_iq)SJt z5CH{ghTb6}z4uTGLVonu zEH4Jics?_#mR-zJxKXeRV)8{=B=Rodn_ zme5=G>ZOUbChL86M)A9M`(hpqYQ-KEL2ygniPp4;zP8IWt0mCdgSP&FaFv)tLF*4& z5S`-LyYq<*8OdSk1kZ7Mn8-+SmYL0CziJy!g}#n9K?P=z?)+71CIfTzc|*z#!S=L| z!P@4$KYf(Gh2lysR>4z&N)LkX53=G!BuS_Y6eY#GO>AX1n&$I0BWw3)`al~${IT?M zW}rMk-#A{|yV9AF)ZYA$s!y?2o~wUUO8*cKOoQJTNW-wNcPo|=xLxLs z2zkgeafi|yLnm~0GVfdj^QH1cp2bQYtg}BX2!TBgsJYL}VxtuxdQ<5RaM=6-_!(z0 z)jgc8mH5y-y7jhW{%iZ{Cq?%L;%;f*fU*t-Xf${@Neo>g4Gh1|+Y87fj{d3_!1^5v zm>%}oJIoft`s4QOpaunWXN@T-kgqvKY@(JMxvV#~i*o3JLG?s8$v-{x$1og%i{$n42CwBIN?6u7~uacuYGh8!5D!d{ zbR$@52t0aaeUqr&@uW%EIz2*Z+}KGrx!!woLN5S%dE05JR5OgZrd^+HkTV`kX8ir) z*b^8~XXi(L`NzJh?3!z=7 zAenzte2u$Fn}+M+=lluPb*nYj10Z;B@lu46P3B!pj@pS0@I=q?SXUq<{KwMeh0Ocm ze;eq1&(@|&I3MUG!QqMI4kn2R9KorQk>aE(p#O1uJRCr=^68-stSGYn)3xQ1r59T_ zNJV4^2_oF)_0I#zmG-UUGDFfkRU6Hs6v=$zwEHKUGdwKXjyACkvFjk6@J)7bg?;#@ zP%_(3IkrwO_9JL7dNA9{??`qLkyGKSyp{xuq;F){^Lzn)bkUsh5EGC)4QXfveTM?S!K!44IvNiGsU6TY|o_s!Jwl`xUgWgFjy1U zdVOeAx4|#U%}Fo1f`Z4qC4^+%4fw$wu>MQ)cqR6E&voH`5@QXHrA&3>*d}P(anWg= z`rOVeXRHrM@+q2&r&-f4RI=h!zc+t#$v_|9Dp|SyjPEtlF=CeLV7l`?4knRlu=;VT z#`%|W@b=(TO|rUvPF;yRW{=_Bb}@tFnf$(rRzw)J@t^*j?;Ypu^IhQmh7zNx2?m$idmky}M1zo>zXed2UBzyd?Odwv%}M->d0(keD!>w|`dNKP!E}c>-;C^!+JGVcz?{jS zUf1b3KK(Iun>{`liogHrgpV>=r+*c_O8-Anx&mURD~1l2U$WIz&XW3vEB1G7Dmk=q zDIQ=t7|x!*>E9hvhEl5%g|gG9s{#EWeRLYb8Dk9KCJTU@%}1Bnc`*2TULqi`3IFck zC{zpxBJqEZai=Dbw=VVo-{pMCwo@1s#&6Yqv!)`juLtv{9jmX1&&=XDNb$Y>?^LE@ zA|d@&0m(aI`p5c|8`2w)*PUt#guC;wTOq;Qo?RW{xdY;kl3Ccot;t78D8hpi{I%&5 z+I@WH8#72K)iL81uNu$_#btSFNIv7e*Ec!?$Ds~ZfOh_V5$>bVyc!_x%x`a1e0qbh zmvPe{g}@v9>;d-8Oj;i|Y}J|)>Lx;aP4MY_5zS}$%3rPBQP8c4+x~J->cM)G^TTHA z$dkx&@0aPjAK}=wA+u%VNnUxs^)}Z;)}GpXI8Ov*)f1ww7qOec?Fj=CBoJq5p7=4p zvtMtz+jtuA_r_N%hb=3-CowO|k(O`}k@C`gvVB_zRJF0bskL4Gc4rc7`+57b>3GNZ zymi&jWsA~-?XyF(+6edMre^-#$Uuao-e?#rWblY3KoqU?YtRWREf!_tS8C)iBHPrT zU37e0^wvhQD6@2@?O)ZdrfpAHCWNK`bmy3L$AKR=ZVg3Fz=qxiu)7NiRjum!b?O21m=kul=tg(=Y z49>-379wNXBjKz@4Do@Tpokj4cA4cb{o_FJ*q2Fx%kYh%%y>jD>R%>;>&9xtUkq%P zcL;V^_S-ILy)f9m*$`eHxSxx&xb$J6OU_TQa;%1_UQE&7J_CoruD(O&Y2gWq5PL$NQpSTXPQUQg2TeSbG>NEm)>{WKsBl_Kn;4m%-{qm!UI< z*C+6w_0jh2UxOK_0U*4%?9%bf#WS$KNbqDYrh0otme|`GuRf^QY?1kc*T18yUFSHC4bcD)qm#Cd1(O~4soh7`jj=* znej)R*MB+@JZ8PlQSl$QxW1u_-r{R|^-!2kIkT~^yeZ3+>N284AKhRYuzfkOLqC7u z$a{0Q76WPgHhUvTz2Q32I2G8~P&iEofBUxAd`2p%KB)75*q}2$S?m9=4LSq9wiLZb z13Cjd!$9Zlqqv^a)A|GN$#Y=#Nt+~C*`o-s_=&4F+$tG{=OFj4k5!3g*P5pwj`i+u zZrX1uQO{s)y^tsNMYykYH}S8>2>${x{_h!d{tNTKha71TUq27HE-sfcrj54iiG4!J z8QtQBK54VPfpXyj%9d!?WGtiAqy&?hm7Ed5RaPv}A(<_Y?HPVQZUaYCa|EvDs0?GCgUWqOt1t z$S0Q}qN*AY#qTbZ592<2J8&(f6teb^@KhMdhFit6Q`q6SyUfn&lhYUvlATos9Df7I z3za25_&n+~r=(5zIS?({E^ybURXvtpUQ&fB@-;g(gzSoK9Wde@c?%UiOK8y#^yLS} z&fM9luU5MJ_B3X^|6`+F;JmP!=rB?b6Le~y6tzxL;- z`W$G+zHHk|za*#e**S&zPE{*~oMKHY?(ar~SAEcbHzKUsj59Gik1e?6pYq|Ugxy-y zQbGC4=zcAsJL+g^4@pwZ?*&3%`S=2elV0$E^;d5Z#_Ol$Nt^lZFD#zx_q0K2NIS9~ zeSOtBO3&UA`H$tUn8mg+b+kEiGfB|paoHC*l^0zO7e$i&URA07NOl4FL({O`Rkd|6 z$6dk7v`J+A)?~>72FU2Tfz%LGRx$l&^)7$}fZB1*{t9;8l-fJjM>?i%86y>QMusR6 zC`lIgIWweXCJq#LZLLw9MLat}lHf2rMtf3>MKZM<8w$N2y*MRdI$u6^P4PP4Ro zx_>Izl%^J55bxfs#mNEwgiaRYds!S7CCM2Q*tJt`ZOivgV8%x8AU1{leR2ln&g8PO8!+!TOCWr_o1STWE2!8Fy6}eQI8n$nI>0PbxJbY|dV$lOHFp&- zmioA0iflfml6s1lBk2hoyui8*CgIwSlpP|Smo;?6q!X^zrqS&^81y6(ObtOdzR2tp ze@<_u5cDtzx9F{lYi-^LO^459B$tZgnZ2X#J(+WSj~J;Ebqys_>IwA@&G#P3?Gjz8 z;wia|*H|p=o`3-l=2Zfl>)7E!PJ^wq6_-{qHDrYEcg# zhgKErzEcN%S$>_%C@y$%rO-JkDaUvn5{NU_M!erpL!dVdt3c{|+zUROTOhf1!>i{i zV(&!KuP!0>n*58RG5s&I1~FBL42CpB#C5KfMJ?(a?Nq|t6uwyd9YH2y*QvRg$gqqx zZOEM(7Q#m~M1x3`*mUX!itptAhb$LLT?IEb2*ugs7K%ra{oalT1>~%fLrc0Jdb5V)ze}UYO zNw`iwzS<_vZ>i>x!-Dv0d=X!CwcM%t)q@W2$JJx%a?&ppnK)hl((F2_Kd6%!!YM|G z4D!dzb_|$mxIe2Ue(apIccF;xj|8gy|87@yK8W6R9C0ghD-_zE7j&Z}`9A*Q21D%6 zy>rVwx@D)9e97Boy=436^x{*7=frsZLNI0PGwuY+R?1bT=qswsE$8t%S{u#g1n9bY ze!-RBmu~hecG=QvklNit!gil>Cp1w-N*t(kI^ZHD9#4ZfUn5kAhJl19b!Swq&)QUS z&fjxKN~rl&98C&Q^4|0=FhiW*#U7U21){K)fJ19E1^j z)|tbdAb9qJpZNRt)wBi=K(zp}e z9cRgIU5mcbmCT)Beth{>u5zTr4NBhgPBU>~l&ufy(0{&Q5r2F+rNGb!bnVA^p`y4G z7+xb>PRZWYPUN4pSw2c$ORwfvB9y$-Bmb=9%AIf%joOMEzDT^wJA4yw z@WfO9=l`lFzZbo~Ogw+T?}+gCH)d2gF-qQ8D4E?odX0vAFzy5i;S+kmh5FyRtVr$z z-HWQs7Q`JQjc#5SM&biwh?PG2ie5Q!jU9LCHD3P3CPh4w$t-;_t_++HmTzCQUhuSIw{4=oI^%5+pP2{!enffmAR7Q zdL)gh)OSh4YNsY2k>{vv!FbUtPkB|~S7_tr67{)yp5K8yFQ(f!rl*5<&PC+a+zjF_ zG&96XU_m5$m&sAriHxb2TXr|Y1RI$=(=V<(?S3aqd-20dsx2&K5gnmJ?v=YB2WEep zMw17}{{mPudL9D=*jA7nenV>&T!SSRyQBFWZmhG?wWPh&q9?zYu6s7# z-BO&(ac&kl-B1m__(BKn$5yJ4=Oh_uU=Y;0gto7N&S5V%VctLC3 z(CdmG7TUEV^!-;W9a&1Ee%3%Ga?t!IAtE{5s=Id74zQc!rCKY(m98|M?Q#wA-y7cs zd-L1Ubmd+k)jtm9598gRRhutom?8gU>%p&(`Jj(3*XK#d*=Jv|MKE=p;n&_*U&ta^ za!$G5T~N!hR*&FNm0h%&$XWu}CX3vHH>x(L`@&1LRJ`NN`_LLin~tKG3YFE3+#d2J zPwtr%`agDMBFEpp^ky2Df4|dD=YVW0Sfq?W^UEe3aquhG zJsIcXD7?d79QKf&AW6|Lv=EXS{7~(yvhu?wAbr2R!|H2=>C@*YLJN?L;D@h0DZbqa zu-)D^@|N~kQCQABoL3*0=7(;mk5@%N!aO04=f~|~Pk|tCK{>3h6XaT)mbosfa_Wz8 zoKgYfm3+Y`lBnoDi8c44NoT!;fOXVp)Qi*zDY}pSNtMIB>Kpd>CFRMM)#3}bjq@Ko zk#~RRzR>SXD#JNqO2W$Ed#5dDm1OD9uzmx994Oj@6+l>d7C{)Moa?uKEp4~O7EiAj z_V|G!{)=fIZ|F%8r> zd@rSR^RzS9*(dH`$NIe5ihNgS+dc*n|FGR!pZq#+CX~5u%-VF@;4vg$_?80p|7b&G3jo^roFQV6Cdd5qU4! zhJO0$i!nK>t2DW9Dz0}dL`|JZaOu_0bQTV>1ttv72sW>Orl_>vIuhbM)!uuy@nd)& z#cO1t*j^lhXAULxThM+QWB(ncM*%`yQHg@aA~~TD;dU-$ig>yLeJzB{)k@U}xfPHy(qs3F7#& zae#b047wH$1T`1e$jQhlvTs~%x*0QfVOVDAv@5pYAT1J+H}>v4Vv~Ac!b1@GB)x!H zn`Fk{eW94fruaJR8q=GKAkL=ZGhhu?jD%0QwCHnj!V*KDrihM%nGe*$b$g<8*-7YD z@#i}2{!@UuKj*IM=c;`17>F@eFl(kig$r465n1oQl@@B^8zYP$41h1|E+M%=Bs!bO zD9fEcBsNV7UliYoB0CC6F{|e+DZSO$Rm@S)W|DUs4Rofud2c-&<`b7eBtLRf#|d&V zn2ykz?zcWnv=ctF;NbeS&=!cJrXCH_LP#`)T;0lH9Cs$JrSxd~h z;Kj9ijsh7mu!W>Lf#SvdT@i+lhw#h2pbs3#n|{{%+{n}cUsvj~Fcf)W?fGY~l2i<* zi5lMIWk3Yw?c@R*(%usoY)|@onoJch13f*=nvx@^oI8wZO*%e26=%;RdO9s=X^g*`6SAqiltocezIl z$3K1=`7^l`tK@-S3>nE^9}jd8jXfvGqH{)Y@=z11d5n;5=KXoyzQa}S zk}G#bdSiL6FHz~_pMAk`j@={!iKrhaM?rTThr&LKppGy2HEotvHC=4&|a;30j^5Az4W9+G_R7>~V0-nSVc}pC?r_AO`@or-cj| ztIh{Ahi-R?M|BxuEr+6Dr@Z9{^W6{GyQ9>)?WD4g`ubKK*CLc}=btV-(!M`k-2Lc- z3Au$vGqVL^B)&}Nc1%j{I-sZjF=dcjOvn$JH-2B*zV%%$(qWGsKYZo7Wo7vI)Quf$ z;}7iCHutFopmF>gmKnfH;0xlwrTjwVvhDuik-INFOFRdP6qv{uf z=y{dr@)LgO|~;hS{((@mj4gADQQ z^m>j3L8uLMfQGca3oUOJ6j@>W(4R$AS5@b+3kz>EZ>R!q7Ln@H@^faeSZh~ssWAk3 zpMG`xn~Rb50%ia{{p!`0pXQKK-!6AU{2)hy@Ux?e$uO4vtfrcK4IDcT#@!{^DYdyz z4f3M`Y$krV$fdjuMBJyNU*~A}9T8bkE8hLSoUa@jS8?3Iqd~!Pf4epDM%e1k7t#Q! z3(D8kfl`VbszA%{4`l_NR+$Bw0L-&Ro?a6*Wu8oM^N%56w@vGJejFLJL=@P{XXBV0 z_Efh&>5z@56LQ?E%#5tuyR&28(yl3FHYX$+yPydybu|?fPOZ%~6#}diZM9=WoQY6L zSBllF~dzUD~8; zxlT7dYVzu=eoG~s;YWu=_~mg`B8jQyhXWnB8U5_`KXCFlx0gp;>(Zjf0y1`AZw!l( z;&KZ~EMVbe%ReyZv#FagXnZs7cN1Y!%R|Crm$mVPr+WIkt=F09`v`Y7t&s_hu}_n3ULyM?}7{#ITPVAqtU z4b|yD8B)SWo{n}9JeNDNOGlRtKEuSj<1~|oXAS(0O6T+PH!|?T9jl6693yE40J?oM zr5U8p5G)oq3hW202Qv=RYh-A4+)ftd`grR@pURj#r@s^dNnz)c5USB_^0G%DFZ;)< z0fK11i}Hdw*L=Kw zQ1Kw;Lov&*+sPN(HrOVJ(4)DkKg2AJ>Byh!s0_M3;4(2GGJe0NiTp|?m0z;2D~K~u zUP?2%gKYXuUw97VS}gtJaB$A85CE`JqKwpx*<2&E91)oH^}KI&vUE8-u4+sfkH%PD zY&V_;gFp$ORDA{-<@dz>-Q`~Cj0NNs;O%z;e7Om}^zXe2w%LK<*S>|zw7{s) z%a;*qTTkXj*pWio8C(eN8g-w?3@cFT=^*84xi{m|SxdbR$35I|i|=vk{%aG~Mb`)& znld;yFu8F}8z1lA=)RCHMir;*!m#Vownumc31I0U%1yY+SE%!2C*%121A^26fx7F| zPd|_xky_rf-`unGVY*Z`E2~buaCO^OueQt%8wjy!JA(1S18J3zl5kXN2hCDuqQglM zJ3eDSU%75+&9fZ}uo(^=kozm%mn_mK?HmyG&-lJ9_(IHo6bBB!Tg+WxnWY6J#qassw^!~EOsIbC|Q zdOryWN%I&^eAFzq9v#jR=q*X&LO zewxmlg-j1FQq{SORc#v0RRD>ff6q?X9f=q=1*EfcAn1eVnMZW*YHIH|PtOWZ!S4U% zIIzF{|8yKkQ$#?Jt+;ykrzFYwxdI%{7Vy2ZssppQg!Qm%#;E|4nZgGUrsq8y@JYWbyxOdX59!_vtHu0Wshv%VP%lE2Z zE0n*9nCQ${q26w)Evk8lDN1J_2IS%}#r3TMCl6Mytmf=LuU@QpiLVAXYzoY z;}`Ulwk>|gg5IA$?((Jm#VOS{iH*&`fsTP;!eJ0>90rB(*sOc_;&4OrY@U8PB5%uo zJfv{8&&;EFtkZiLjHQ&GR%zLDH&uoxf6RiDO#AtX{{p|GsQNLnzxef_n%1G}v>!B% z)lA17%V5biBiw}mp5-V1JQD={4G8>D={WKB@HSA4@c1xU8ZA9rJKW3rODYJvrDMNJ zx;M6O=6UvfuH0U{@qopVsq2vAW2})*?O7+$7N4-^_8uOfnGuuRP?;oCwb^;!Ou2B^UPs!FRk z^Vv7weJKI9fK|5r5?llZe~XY;_8tGi1@3ss7^dyF(k^p81u-V+42GPR_Y&v!2$fmz zn@@ABBP`rzP5@ckAU7O_S~*i(8dDx5ewc1>`lI;o49dzX2ydH z|6WJlDIs1nckghN@L1hzS9H!>t}e1}`aN*c%ZtRjk*fEN2a(~+_jq-2WAg1g+_YDL zvsnF>Z|5}iS!ff@E9Bx8)akLT+~_D+X2h&cZdKS>XFEIgIHn%!M%COVb4+Ws4mMin z1!J@3wvE;L5Z!bAk^KtuYCp@6>vyWgg+?FlIOsjGsv{Yq2@J}_9qY4o;r|ivkX^65 zPH#noJ$xP3C8T88I&Vz76(cj(C%2BsjUw`R?tG$3ATk6&L|}0a3_F@w?)i!5^UjnT zZS9li)_22H)f&~KyVDY1HchUaY+r=NPtmMfS{oUtY;U{;69u=Hvi2d}A!FicC;zk9 z2We8@s{Y;LL)UIs06J`k#3?j0i#VA{*G$SGg%22e2>scF$PppbP8*yp`vKVPk!?;5 zG`^!ES{Tg4EYV=&g(1Rc`Q!=$u2KunpDW*8215W&UCF0+7hx0gkh-Wq(k@Yno5KdG*9bi$;(YBdtP@3 zClh3Qo(c#bN999@$X^5}?mi$1-lg1=$GQEnOI-eHpOzNy#{uLe$N(wcl zK3;y(0M_p4{`L!IByW~>yt%aO^y>?%*|ZLS!VKEYv#u)NMLC?o9UyG zBh6&9lYG!6(Urs1zsTWNM!ae@}s+?wA0M?yElEqTU$( zFf@URd~5Y_L3|zkh!JYgQTl{S0hW!BP$BKfUyUn0N(;nFIGWjkGE@d3(FagZcCDlN?Q2S?0XzlD!N;k#K)zMY*{>>FE?xl(8Pr%;@ z!fD64OVB}ZV$9su>}9=QkBOEf=OrhX$ZLzQx#i<)2Z#WOK#1vf9Cx^^XKc2sqzcK^ z(ee2A2;)FmJ+^ZksvHMep>d;)q20OosAH>pDcNx2en`~_M4%cM?M#N~!idLlX@8Vc z@BbheL`%AQQ1s>8Hp^2IcMgwOxd6DvJTtg!{8chiIlgHT%6}`|XV@9a#+5g;w7Y(F zg@||z^RAwasCKvO3cq#BPUY%IHz+WMxNrMMq;$@(I;Mo|l0ih`+eM14zh;E1J4OFa zMkpRR_kN~YG%VMLEJj4gT5n#MvTxzX9zQiw|7%ifrjT_h&d+?+S^2PT<^wEf zE{Q0;O_|j+NY{O|Z1B)ewgi1;+kI!(5E?ReTy#Nn{BP+XdhW^Fe~f>%@1j3#XAn_q zDE(&^Ly(ehBDnEsGFX+(HPVOeVzO^ZmVQ@ufokRp~m$KT4F51A3HD{XIl&OC`wu1Whj@PY^!mblx< zRp66D6y*SRKK!W_{>CmDmjc)xG&nutVcZRRPRs|(p7q;rgck|bsp~iRsfSPqg}-a1 zGa_P*d9|VQ@l$rx$^2D~KC^>YCpA9o9=i`eS#@M`z7a{xXLoo=8% zrbZ}?#zgFo=6j7zu1c+Mk1~elo0_^R8;CeqBc*M^iHD%o_3aKv@w%RWma-xoIRq+5Z7t_&YGDBKi+~Nl_CiR*e9%d=X`t)`j~G z{E-!o#vH2lEQu!r_cX9HfmidX9z{w;JWE+YWD|R;Y6UQ$y-bt~|KMefzoLbspE7w9 z*%0UP;5^gv{eK;-=v33`;s>) zV>M|dqURV0D2OyrLh(VtBAsOA1*X>nb{Y*?0JVVVK?6Dt-l3)rmaM%2Esd&Y4<(0= z?{qytI&&bKk|l*}dul|1H*YoqO1`YQ(q-8lU7>@d4ib%8iwP;b&c%Cojx?(vw#Wr1o&bu;PKIP^pSG{9C#F!dW2^uMg;le2JFQ2sG3XpgKGT)#Gqn!rptF-E8f!YRCi>bwD$` zVY$iX)SJaLKl&m|uW>@jO=)8CB8M%%L-4F2j_?ERGdwVBebL^j#bT{(l9knLZgqZ= z^)q;7!_<>iznUG@TV-k;Db?CYCGziVj28~2jOyjz`L%RMAK1K9VKPLB5-e*V?R%Bk z>tX$I^p(BoxRbR-U1RRf(=fW~+krAujdi)A2o8Q^0OP4!X7eqx0b$p~EQV$51qCG2 zx3w33?UXCgrOLD{`MbGCdKt(PDl4#DjoE6YLDr=SR=3UK+FBW|jP|LufsyZLh*|rV zdtMj%XdT=>>`7OsqeD}F4A<7}lva3qrvLiWTm~=uWMQ#vrW&AM8~m_jh<2w)Tj~2^ zdTvbDgot=3b34T=0e&GR*ds7)oym#YM|V%!2Cm_fkt)z z+~}*?g2XQod+HL|6Yc2i338d5*?8F9lLZm|kzp(U%?e^?ZG{teBwL#b^p5fadKX!> z0)5HSqF<5Ad+DLdI8vv+dWE{P_1|eH)XmO)DzDq5;+zrL3ctT+!hkl;7Qni>6S8Uo z>6ZK^e~nDh8(4^JfRVM!*24p1QKg#&hNj3a>%iN%s|#}~RD8#DvzZMIYV(B=^bc2( z^f_DaPfb|mJ{Eio9~isM;~V#Rn|2BsADFn_L=Y) zR}i+AINi@=9_ql?2v2=ecEhZ*+Njm!r_Tey`l+v9o`u8-&4k1ysfXn&MycqDa?mt} z2AE0Ij~vo=am*J)inJcmZoR#+!jaub+I$Tt8#mW8+#|l~Z96?Oz>aY&MyIdXhKBP# zITZOtF#1Kvu_pi>2+(XipxuZIPA~a(W7EtWiDH3(9HijHhfU^L&e|tB$6>I_BgPCq zS+93>y+_j>ddXd}n{c7)Fcn=)h|;v=Z35=m${}Oiq(}22yI;zf0oF_i$66)+rHSA> zi3;>G>+(x?!K|~1S={0}#?J=qVU8ez_gNk~H~A|s#}Hu7giT%-!0K0lPi&(n(3#49 z%(??@gyP52A3%ZNk`BnPm?<;Jqabx1vFy#G(%sT-wN(P$$#abxT!8-t}-+^8S}Ky}g+y+oMsg zSCqrhDdHn-X5?z^*V~_7MIB&nf2Th>PVW55xA-=}O3JJAE15EQYGHBsT3IR9257cC ztnhjrV^hk`$(t=8788hg=X75Wc07TjZWdi8UbT@5AWcS`DzdNojd*AtB6%0OuS^e+@gGj6wXVY}LZ9Zl{yIT) z?d)##>~tsL?)T3%Y_D&KVe|O9Z!?^Ucj%)(1%gK0#BDS{)o4I#aILxHsarJA%Qp~p zh^!r{sjZf)2_n`&Ia=*Kex-T@hV7(OHPn8|&n;I(KKr~{3{I`AtIP4Iy}1-8bF?A$ogCx-prffABwTFeN<32w`!O_6lR|UwCIt8G2xENT>+8}AGTG&4 zl|*E5#{Lv@i$B?7`qdWu9trvO-k(kSEOgsNQnm~GyBBvrVwK41+Pm46L8@}&Y}2oW zl|D6eCix}L29KW%*<*cztZYN6q^76ezOFSm6V~@&s{skPseJQ1QcvJoUP+U+sFDs= zMr3`s3PW0x(HE>A8J2$)Z0j|HJV4%j337&-Jc%%--E9mX$A&rgY?a(zs47Eyh^TbD z=&xBgEJKHjM3BV8rc#GN-tEp7`{SnJNsKw}KUOXFF{Zd!ZaC(6ITjusUdxzc|6|p1 zKg2W~&ghJ-Et~d0D{*&<8z3w{8z&nL;@S+|B? zn{X&)?v{IxNPnt-m%Z3Zg>g;vtO;AoUnjI%eZe>;ubf#o1gtxo-rmihHDh%@k-5#4 zQ421F57;(3d;6>V gt_grouped_summaries(Environment BM, const IntegerVector& rowInd, const IntegerVector& colInd, const IntegerVector& groupIds, int ngroups, int ncores); +RcppExport SEXP _tidypopgen_gt_grouped_summaries(SEXP BMSEXP, SEXP rowIndSEXP, SEXP colIndSEXP, SEXP groupIdsSEXP, SEXP ngroupsSEXP, SEXP ncoresSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< Environment >::type BM(BMSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type rowInd(rowIndSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type colInd(colIndSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type groupIds(groupIdsSEXP); + Rcpp::traits::input_parameter< int >::type ngroups(ngroupsSEXP); + Rcpp::traits::input_parameter< int >::type ncores(ncoresSEXP); + rcpp_result_gen = Rcpp::wrap(gt_grouped_summaries(BM, rowInd, colInd, groupIds, ngroups, ncores)); + return rcpp_result_gen; +END_RCPP +} // SNPHWE2 double SNPHWE2(int32_t obs_hets, int32_t obs_hom1, int32_t obs_hom2, uint32_t midp); RcppExport SEXP _tidypopgen_SNPHWE2(SEXP obs_hetsSEXP, SEXP obs_hom1SEXP, SEXP obs_hom2SEXP, SEXP midpSEXP) { @@ -140,6 +156,7 @@ END_RCPP static const R_CallMethodDef CallEntries[] = { {"_tidypopgen_gt_grouped_alt_freq_diploid", (DL_FUNC) &_tidypopgen_gt_grouped_alt_freq_diploid, 6}, {"_tidypopgen_gt_grouped_missingness", (DL_FUNC) &_tidypopgen_gt_grouped_missingness, 6}, + {"_tidypopgen_gt_grouped_summaries", (DL_FUNC) &_tidypopgen_gt_grouped_summaries, 6}, {"_tidypopgen_SNPHWE2", (DL_FUNC) &_tidypopgen_SNPHWE2, 4}, {"_tidypopgen_SNPHWE_t", (DL_FUNC) &_tidypopgen_SNPHWE_t, 4}, {"_tidypopgen_SNPHWE_midp_t", (DL_FUNC) &_tidypopgen_SNPHWE_midp_t, 4}, diff --git a/src/gt_grouped_summaries.cpp b/src/gt_grouped_summaries.cpp new file mode 100644 index 00000000..d7daf9c5 --- /dev/null +++ b/src/gt_grouped_summaries.cpp @@ -0,0 +1,52 @@ +/******************************************************************************/ + +#include + +/******************************************************************************/ + +// [[Rcpp::export]] +ListOf gt_grouped_summaries(Environment BM, + const IntegerVector& rowInd, + const IntegerVector& colInd, + const IntegerVector& groupIds, + int ngroups, + int ncores) { + + XPtr xpBM = BM["address"]; + SubBMCode256Acc macc(xpBM, rowInd, colInd, BM["code256"], 1); + + size_t n = macc.nrow(); // number of individuals + size_t m = macc.ncol(); // number of loci + + NumericMatrix freq(m, ngroups); + NumericMatrix ref_freq(m, ngroups); + NumericMatrix valid_alleles(m, ngroups); + NumericMatrix heterozygotes(m, ngroups); + +#pragma omp parallel for num_threads(ncores) + for (size_t j = 0; j < m; j++) { + for (size_t i = 0; i < n; i++) { + double x = macc(i, j); + if (x>-1){ + freq(j, groupIds[i]) += x; + valid_alleles(j, groupIds[i]) +=2; + if (x==1){ + heterozygotes(j, groupIds[i]) +=2; // we add 2 so that we can then divide by valid alleles + } + } + } + // now for each group, divide freq by valid_alleles + for (size_t group_i = 0; group_i < ngroups; group_i++) { + freq(j, group_i) = freq(j, group_i) / valid_alleles(j, group_i); + ref_freq(j, group_i) = 1-freq(j, group_i); + heterozygotes(j, group_i) = heterozygotes(j, group_i) / valid_alleles(j, group_i); + } + } + + return List::create(_["freq_alt"] = freq, + _["freq_ref"] = ref_freq, + _["n"] = valid_alleles, + _["het_obs"] = heterozygotes); +} + +/******************************************************************************/ diff --git a/tests/testthat/test_loci_ld_clump.R b/tests/testthat/test_loci_ld_clump.R index 4d5e83f8..17d879d1 100644 --- a/tests/testthat/test_loci_ld_clump.R +++ b/tests/testthat/test_loci_ld_clump.R @@ -11,7 +11,7 @@ test_loci <- data.frame(name=paste0("rs",1:6), allele_alt = c("T","C", NA,"C","G","A")) test_gt <- gen_tibble(x = test_genotypes, indiv_meta = test_indiv_meta, loci = test_loci, - backingfile = tempfile()) + backingfile = tempfile(), quiet = TRUE) test_that("ld clumping runs",{ diff --git a/tests/testthat/test_ploidy_vcf.R b/tests/testthat/test_ploidy_vcf.R new file mode 100644 index 00000000..708f61a2 --- /dev/null +++ b/tests/testthat/test_ploidy_vcf.R @@ -0,0 +1,9 @@ +test_that("import a vcf with multiple ploidy",{ + vcf_path <- system.file("/extdata/ploidy_test.vcf.gz", + package = "tidypopgen") + test_gt <- gen_tibble(vcf_path, backingfile = tempfile(), quiet = TRUE) + # it's mixed ploidy + expect_true(show_ploidy(test_gt)==0) + # individuals are either 2 or 4 + expect_true(all(indiv_ploidy(test_gt) %in% c(2,4))) +}) From e87c242f462cf139586cf4419c36321706c76d84 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Wed, 15 May 2024 18:15:18 +0100 Subject: [PATCH 12/15] Use fast cpp code for fst --- R/pairwise_pop_fst.R | 164 ++++++++++++++----------- man/pairwise_pop_fst.Rd | 6 +- man/vcf_to_fbm.Rd | 2 +- src/gt_grouped_summaries.cpp | 3 + tests/testthat/test_pairwise_pop_fst.R | 3 +- 5 files changed, 104 insertions(+), 74 deletions(-) diff --git a/R/pairwise_pop_fst.R b/R/pairwise_pop_fst.R index 1ba4e68e..62838ef9 100644 --- a/R/pairwise_pop_fst.R +++ b/R/pairwise_pop_fst.R @@ -20,6 +20,7 @@ #' or as a single genome wide value obtained by taking the ratio of the mean numerator #' and denominator (FALSE, the default). #' @param method one of 'Hudson', 'Nei86', 'Nei87', and 'WC84' +#' @param n_cores number of cores to be used, it defaults to [bigstatsr::nb_cores()] #' @returns a tibble of genome-wide pairwise Fst values with each pairwise combination #' as a row if "by_locus=FALSE", else #' a list including the tibble of genome-wide values as well as a matrix with pairwise @@ -32,7 +33,9 @@ # #' returns a matrix. THIS IS NOT IMPLEMENTED YET. -pairwise_pop_fst <- function(.x, by_locus=FALSE, method= c("Hudson","Nei87","Nei86","WC84")){ +pairwise_pop_fst <- function(.x, by_locus=FALSE, + method= c("Hudson","Nei87","Nei86","WC84"), + n_cores = bigstatsr::nb_cores()){ if (!inherits(.x,"grouped_df")){ stop (".x should be a grouped df") } @@ -48,7 +51,7 @@ pairwise_pop_fst <- function(.x, by_locus=FALSE, method= c("Hudson","Nei87","Nei } } -pairwise_pop_fst_hudson <- function(.x, by_locus=FALSE){ +pairwise_pop_fst_hudson <- function(.x, by_locus=FALSE, n_cores = bigstatsr::nb_cores()){ message("this function is not properly tested yet!!!") message("if you have time, test the results against something like hierfstat and create a unit test") @@ -62,16 +65,22 @@ pairwise_pop_fst_hudson <- function(.x, by_locus=FALSE){ Fst_locus <- matrix(NA_real_, nrow = count_loci(.x), ncol = ncol(pairwise_combn)) } # summarise population frequencies - pop_freqs_df <- group_map(.x, .f=~.gt_pop_freqs(.x)) + pop_freqs_df <- gt_grouped_summaries(.gt_get_bigsnp(.x)$genotypes, + rowInd = .gt_bigsnp_rows(.x), + colInd = .gt_bigsnp_cols(.x), + groupIds = dplyr::group_indices(.x)-1, + ngroups = nrow(.group_levels), + ncores = n_cores) + for (i_col in seq_len(ncol(pairwise_combn))){ pop1 <- pairwise_combn[1,i_col] pop2 <- pairwise_combn[2,i_col] - numerator <- (pop_freqs_df[[pop1]]$freq_alt-pop_freqs_df[[pop2]]$freq_alt)^2 - - (pop_freqs_df[[pop1]]$freq_alt*pop_freqs_df[[pop1]]$freq_ref)/ (pop_freqs_df[[pop1]]$n -1) - - (pop_freqs_df[[pop2]]$freq_alt*pop_freqs_df[[pop2]]$freq_ref)/ (pop_freqs_df[[pop2]]$n -1) - denominator <- pop_freqs_df[[pop1]]$freq_alt * pop_freqs_df[[pop2]]$freq_ref + - pop_freqs_df[[pop2]]$freq_alt * pop_freqs_df[[pop1]]$freq_ref + numerator <- (pop_freqs_df$freq_alt[,pop1]-pop_freqs_df$freq_alt[,pop2])^2 - + (pop_freqs_df$freq_alt[,pop1]*pop_freqs_df$freq_ref[,pop1])/ (pop_freqs_df$n[,pop1] -1) - + (pop_freqs_df$freq_alt[,pop2]*pop_freqs_df$freq_ref[,pop2])/ (pop_freqs_df$n[,pop2] -1) + denominator <- pop_freqs_df$freq_alt[,pop1] * pop_freqs_df$freq_ref[,pop2] + + pop_freqs_df$freq_alt[,pop2] * pop_freqs_df$freq_ref[,pop1] if (by_locus){ Fst_locus[,i_col] = numerator/denominator } @@ -95,8 +104,11 @@ pairwise_pop_fst_hudson <- function(.x, by_locus=FALSE){ -# the implementation for Nei 87, adapted from hierfstat -pairwise_pop_fst_nei87 <- function(.x, by_locus = FALSE){ +# based on the formula in Bhatia 2013 +pairwise_pop_fst_wc84 <- function(.x, by_locus=FALSE, n_cores = bigstatsr::nb_cores()){ + + message("this function is not properly tested yet!!!") + message("if you have time, test the results against something like hierfstat and create a unit test") # get the populations .group_levels = .x %>% group_keys() # create all combinations @@ -107,39 +119,30 @@ pairwise_pop_fst_nei87 <- function(.x, by_locus = FALSE){ Fst_locus <- matrix(NA_real_, nrow = count_loci(.x), ncol = ncol(pairwise_combn)) } # summarise population frequencies - pop_freqs_df <- group_map(.x, .f=~.gt_pop_freqs(.x)) + pop_freqs_df <- gt_grouped_summaries(.gt_get_bigsnp(.x)$genotypes, + rowInd = .gt_bigsnp_rows(.x), + colInd = .gt_bigsnp_cols(.x), + groupIds = dplyr::group_indices(.x)-1, + ngroups = nrow(.group_levels), + ncores = n_cores) for (i_col in seq_len(ncol(pairwise_combn))){ pop1 <- pairwise_combn[1,i_col] pop2 <- pairwise_combn[2,i_col] - - n <-cbind(pop_freqs_df[[pop1]]$n,pop_freqs_df[[pop2]]$n)/2 - sHo <-cbind(pop_freqs_df[[pop1]]$het_obs,pop_freqs_df[[pop2]]$het_obs) - mHo <- apply(sHo, 1, mean, na.rm = TRUE) - freq_alt <- cbind(pop_freqs_df[[pop1]]$freq_alt, pop_freqs_df[[pop2]]$freq_alt) - freq_ref <- cbind(pop_freqs_df[[pop1]]$freq_ref, pop_freqs_df[[pop2]]$freq_ref) - - # sum of squared frequencies - sp2 <- freq_alt^2+freq_ref^2 - Hs <- (1 - sp2 - sHo/2/n) - Hs <- n/(n - 1) * Hs - np <- apply(n, 1, fun <- function(x) sum(!is.na(x))) - # mean sample size over the populations - mn <- apply(n, 1, fun <- function(x) { - sum(!is.na(x))/sum(1/x[!is.na(x)]) - }) - # mean sum of square frequencies - msp2 <- apply(sp2, 1, mean, na.rm = TRUE) - mp2 <- rowMeans(freq_alt)^2+rowMeans(freq_ref)^2 - mHs <- mn/(mn - 1) * (1 - msp2 - mHo/2/mn) - Ht <- 1 - mp2 + mHs/mn/np - mHo/2/mn/np - - Dst <- Ht - mHs - Dstp <- np/(np - 1) * Dst - Htp = mHs + Dstp + n1 <- pop_freqs_df$n[,pop1] + n2 <- pop_freqs_df$n[,pop2] + p1 <- pop_freqs_df$freq_alt[,pop1] + p2 <- pop_freqs_df$freq_alt[,pop2] + q1 <- pop_freqs_df$freq_ref[,pop1] + q2 <- pop_freqs_df$freq_ref[,pop2] + a <- (n1*n2)/(n1+n2) + b <- (1/(n1+n2-2))*((n1*p1*q1)+(n2*p2*q2)) + c <- (p1-p2)^2 + numerator <- 2*a*b + denominator <- a*c+(2*a-1)*b if (by_locus){ - Fst_locus[,i_col] = Dstp/Htp + Fst_locus[,i_col] = 1-numerator/denominator } - Fst_tot[i_col]<-mean(Dstp)/mean(Htp) + Fst_tot[i_col]<-1-mean(numerator)/mean(denominator) } # format nicely the objects group_combinations <- cbind(.group_levels[pairwise_combn[1,],],.group_levels[pairwise_combn[2,],]) @@ -156,9 +159,8 @@ pairwise_pop_fst_nei87 <- function(.x, by_locus = FALSE){ } } - # based on the formula in Bhatia 2013 -pairwise_pop_fst_wc84 <- function(.x, by_locus=FALSE){ +pairwise_pop_fst_nei86 <- function(.x, by_locus=FALSE, n_cores = bigstatsr::nb_cores()){ message("this function is not properly tested yet!!!") message("if you have time, test the results against something like hierfstat and create a unit test") @@ -172,25 +174,24 @@ pairwise_pop_fst_wc84 <- function(.x, by_locus=FALSE){ Fst_locus <- matrix(NA_real_, nrow = count_loci(.x), ncol = ncol(pairwise_combn)) } # summarise population frequencies - pop_freqs_df <- group_map(.x, .f=~.gt_pop_freqs(.x)) + pop_freqs_df <- gt_grouped_summaries(.gt_get_bigsnp(.x)$genotypes, + rowInd = .gt_bigsnp_rows(.x), + colInd = .gt_bigsnp_cols(.x), + groupIds = dplyr::group_indices(.x)-1, + ngroups = nrow(.group_levels), + ncores = n_cores) for (i_col in seq_len(ncol(pairwise_combn))){ pop1 <- pairwise_combn[1,i_col] pop2 <- pairwise_combn[2,i_col] - n1 <- pop_freqs_df[[pop1]]$n - n2 <- pop_freqs_df[[pop2]]$n - p1 <- pop_freqs_df[[pop1]]$freq_alt - p2 <- pop_freqs_df[[pop2]]$freq_alt - q1 <- pop_freqs_df[[pop1]]$freq_ref - q2 <- pop_freqs_df[[pop2]]$freq_ref - a <- (n1*n2)/(n1+n2) - b <- (1/(n1+n2-2))*((n1*p1*q1)+(n2*p2*q2)) - c <- (p1-p2)^2 - numerator <- 2*a*b - denominator <- a*c+(2*a-1)*b + p1 <- pop_freqs_df$freq_alt[,pop1] + p2 <- pop_freqs_df$freq_alt[,pop2] + pbar <- (p1+p2)/2 + numerator <- ((p1-p2)^2) + denominator <- (2*pbar*(1-pbar)) if (by_locus){ - Fst_locus[,i_col] = 1-numerator/denominator + Fst_locus[,i_col] = numerator/denominator } - Fst_tot[i_col]<-1-mean(numerator)/mean(denominator) + Fst_tot[i_col]<-mean(numerator)/mean(denominator) } # format nicely the objects group_combinations <- cbind(.group_levels[pairwise_combn[1,],],.group_levels[pairwise_combn[2,],]) @@ -207,11 +208,13 @@ pairwise_pop_fst_wc84 <- function(.x, by_locus=FALSE){ } } -# based on the formula in Bhatia 2013 -pairwise_pop_fst_nei86 <- function(.x, by_locus=FALSE){ - message("this function is not properly tested yet!!!") - message("if you have time, test the results against something like hierfstat and create a unit test") + +## use tidyr::pivot_wider to turn into a matrix if that's what is requested. + +### Faster versions +# the implementation for Nei 87, adapted from hierfstat +pairwise_pop_fst_nei87 <- function(.x, by_locus = FALSE, n_cores = bigstatsr::nb_cores()){ # get the populations .group_levels = .x %>% group_keys() # create all combinations @@ -222,19 +225,44 @@ pairwise_pop_fst_nei86 <- function(.x, by_locus=FALSE){ Fst_locus <- matrix(NA_real_, nrow = count_loci(.x), ncol = ncol(pairwise_combn)) } # summarise population frequencies - pop_freqs_df <- group_map(.x, .f=~.gt_pop_freqs(.x)) + pop_freqs_df <- gt_grouped_summaries(.gt_get_bigsnp(.x)$genotypes, + rowInd = .gt_bigsnp_rows(.x), + colInd = .gt_bigsnp_cols(.x), + groupIds = dplyr::group_indices(.x)-1, + ngroups = nrow(.group_levels), + ncores = n_cores) for (i_col in seq_len(ncol(pairwise_combn))){ pop1 <- pairwise_combn[1,i_col] pop2 <- pairwise_combn[2,i_col] - p1 <- pop_freqs_df[[pop1]]$freq_alt - p2 <- pop_freqs_df[[pop2]]$freq_alt - pbar <- (p1+p2)/2 - numerator <- ((p1-p2)^2) - denominator <- (2*pbar*(1-pbar)) + + n <-pop_freqs_df$n[,c(pop1,pop2)]/2 + sHo <-pop_freqs_df$het_obs[,c(pop1,pop2)] + mHo <- apply(sHo, 1, mean, na.rm = TRUE) + freq_alt <- pop_freqs_df$freq_alt[,c(pop1,pop2)] + freq_ref <- pop_freqs_df$freq_ref[,c(pop1,pop2)] + + # sum of squared frequencies + sp2 <- freq_alt^2+freq_ref^2 + Hs <- (1 - sp2 - sHo/2/n) + Hs <- n/(n - 1) * Hs + np <- apply(n, 1, fun <- function(x) sum(!is.na(x))) + # mean sample size over the populations + mn <- apply(n, 1, fun <- function(x) { + sum(!is.na(x))/sum(1/x[!is.na(x)]) + }) + # mean sum of square frequencies + msp2 <- apply(sp2, 1, mean, na.rm = TRUE) + mp2 <- rowMeans(freq_alt)^2+rowMeans(freq_ref)^2 + mHs <- mn/(mn - 1) * (1 - msp2 - mHo/2/mn) + Ht <- 1 - mp2 + mHs/mn/np - mHo/2/mn/np + + Dst <- Ht - mHs + Dstp <- np/(np - 1) * Dst + Htp = mHs + Dstp if (by_locus){ - Fst_locus[,i_col] = numerator/denominator + Fst_locus[,i_col] = Dstp/Htp } - Fst_tot[i_col]<-mean(numerator)/mean(denominator) + Fst_tot[i_col]<-mean(Dstp)/mean(Htp) } # format nicely the objects group_combinations <- cbind(.group_levels[pairwise_combn[1,],],.group_levels[pairwise_combn[2,],]) @@ -253,8 +281,4 @@ pairwise_pop_fst_nei86 <- function(.x, by_locus=FALSE){ -## use tidyr::pivot_wider to turn into a matrix if that's what is requested. - - - diff --git a/man/pairwise_pop_fst.Rd b/man/pairwise_pop_fst.Rd index c35011b9..465b1dce 100644 --- a/man/pairwise_pop_fst.Rd +++ b/man/pairwise_pop_fst.Rd @@ -7,7 +7,8 @@ pairwise_pop_fst( .x, by_locus = FALSE, - method = c("Hudson", "Nei87", "Nei86", "WC84") + method = c("Hudson", "Nei87", "Nei86", "WC84"), + n_cores = bigstatsr::nb_cores() ) } \arguments{ @@ -18,6 +19,8 @@ or as a single genome wide value obtained by taking the ratio of the mean numera and denominator (FALSE, the default).} \item{method}{one of 'Hudson', 'Nei86', 'Nei87', and 'WC84'} + +\item{n_cores}{number of cores to be used, it defaults to \code{\link[bigstatsr:reexports]{bigstatsr::nb_cores()}}} } \value{ a tibble of genome-wide pairwise Fst values with each pairwise combination @@ -25,7 +28,6 @@ as a row if "by_locus=FALSE", else a list including the tibble of genome-wide values as well as a matrix with pairwise Fst by locus with loci as rows and and pairwise combinations as columns. -a matrix } \description{ This function computes pairwise Fst. The following methods are implemented: diff --git a/man/vcf_to_fbm.Rd b/man/vcf_to_fbm.Rd index 1dee0022..d9550174 100644 --- a/man/vcf_to_fbm.Rd +++ b/man/vcf_to_fbm.Rd @@ -4,7 +4,7 @@ \alias{vcf_to_fbm} \title{Convert vcf to FBM.} \usage{ -vcf_to_fbm(vcf_path, loci_per_chunk = 1000, backingfile = NULL, quiet = FALSE) +vcf_to_fbm(vcf_path, loci_per_chunk = 1e+05, backingfile = NULL, quiet = FALSE) } \arguments{ \item{vcf_path}{the path to the vcf} diff --git a/src/gt_grouped_summaries.cpp b/src/gt_grouped_summaries.cpp index d7daf9c5..d65010a5 100644 --- a/src/gt_grouped_summaries.cpp +++ b/src/gt_grouped_summaries.cpp @@ -3,6 +3,9 @@ #include /******************************************************************************/ +// Summarise a grouped gen_tibble +// this only works for diploid data + // [[Rcpp::export]] ListOf gt_grouped_summaries(Environment BM, diff --git a/tests/testthat/test_pairwise_pop_fst.R b/tests/testthat/test_pairwise_pop_fst.R index ea97a6c5..f8ed5108 100644 --- a/tests/testthat/test_pairwise_pop_fst.R +++ b/tests/testthat/test_pairwise_pop_fst.R @@ -17,12 +17,13 @@ test_loci <- data.frame(name=paste0("rs",1:6), test_gt <- gen_tibble(x = test_genotypes, loci = test_loci, indiv_meta = test_indiv_meta, quiet = TRUE) -test_that("pop_fst and pop_fist compute correctly",{ +test_that("pairwise_pop_fst compute correctly",{ test_gt <- test_gt %>% dplyr::group_by(population) test_hier <- gt_as_hierfstat(test_gt) # compare results against hierfstat for Nei87 (Nei86 does not correct for Ho # when computing Ht, so it gives a different result) nei_gt <- test_gt %>% pairwise_pop_fst(method="Nei87") + nei_hier <- hierfstat::pairwise.neifst(test_hier) # hiefstat values are rounded to 4 dp expect_true(all.equal(tidy_dist_matrix(nei_hier)$value, round(nei_gt$value,4))) From fabdaf7568c5734d7f69a9095a58ded80f364c49 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Wed, 15 May 2024 20:57:39 +0100 Subject: [PATCH 13/15] fix gt_extract_f2 --- NAMESPACE | 1 + R/gt_to_aftable.R | 165 +++++++++++++++++++++++++++++++++--- R/utils-pipe.R | 1 + man/gt_extract_f2.Rd | 84 ++++++++++++++---- man/gt_extract_f2_broken.Rd | 41 +++++++++ 5 files changed, 262 insertions(+), 30 deletions(-) create mode 100644 man/gt_extract_f2_broken.Rd diff --git a/NAMESPACE b/NAMESPACE index 7bbb88cf..f8c72123 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -130,6 +130,7 @@ importFrom(Rcpp,sourceCpp) importFrom(generics,augment) importFrom(generics,tidy) importFrom(ggplot2,autoplot) +importFrom(magrittr,"%<>%") importFrom(magrittr,"%>%") importFrom(rlang,":=") useDynLib(tidypopgen, .registration = TRUE) diff --git a/R/gt_to_aftable.R b/R/gt_to_aftable.R index b3e079fa..7398e651 100644 --- a/R/gt_to_aftable.R +++ b/R/gt_to_aftable.R @@ -1,5 +1,86 @@ -# admixtools equivalent functions +#' Compute and store blocked f2 statistics +#' +#' This function prepares data for various other *ADMIXTOOLS 2* functions. It +#' takes a [`gen_tibble`], +#' computes allele frequencies and blocked f2-statistics for selected populations, +#' and writes the results to `outdir`. +#' @export +#' @param .x a [`gen_tibble`] +#' @param outdir Directory where data will be stored. +#' @param blgsize SNP block size in Morgan. Default is 0.05 (5 cM). If `blgsize` is 100 or greater, if will be interpreted as base pair distance rather than centimorgan distance. +#' @param maxmem Maximum amount of memory to be used. If the required amount of memory exceeds `maxmem`, allele frequency data will be split into blocks, and the computation will be performed separately on each block pair. This doesn't put a precise cap on the amount of memory used (it used to at some point). Set this parameter to lower values if you run out of memory while running this function. Set it to higher values if this function is too slow and you have lots of memory. +#' @param maxmiss Discard SNPs which are missing in a fraction of populations higher than `maxmiss` +#' @param minmaf Discard SNPs with minor allele frequency less than `minmaf` +#' @param maxmaf Discard SNPs with minor allele frequency greater than than `maxmaf` +#' @param minac2 Discard SNPs with allele count lower than 2 in any population (default `FALSE`). This option should be set to `TRUE` when computing f3-statistics where one population consists mostly of pseudohaploid samples. Otherwise heterozygosity estimates and thus f3-estimates can be biased. `minac2 == 2` will discard SNPs with allele count lower than 2 in any non-singleton population (this option is experimental and is based on the hypothesis that using SNPs with allele count lower than 2 only leads to biases in non-singleton populations). While the `minac2` option discards SNPs with allele count lower than 2 in any population, the \code{\link{qp3pop}} function will only discard SNPs with allele count lower than 2 in the first (target) population (when the first argument is the prefix of a genotype file). +#' @param outpop Keep only SNPs which are heterozygous in this population +#' @param outpop_scale Scale f2-statistics by the inverse `outpop` heteroygosity (`1/(p*(1-p))`). Providing `outpop` and setting `outpop_scale` to `TRUE` will give the same results as the original *qpGraph* when the `outpop` parameter has been set, but it has the disadvantage of treating one population different from the others. This may limit the use of these f2-statistics for other models. +#' @param transitions Set this to `FALSE` to exclude transition SNPs +#' @param transversions Set this to `FALSE` to exclude transversion SNPs +#' @param auto_only Keep only SNPs on chromosomes 1 to 22 +#' @param keepsnps SNP IDs of SNPs to keep. Overrides other SNP filtering options +#' @param overwrite Overwrite existing files in `outdir` +#' @param adjust_pseudohaploid Genotypes of pseudohaploid samples are usually coded as `0` or `2`, even though only one allele is observed. `adjust_pseudohaploid` ensures that the observed allele count increases only by `1` for each pseudohaploid sample. If `TRUE` (default), samples that don't have any genotypes coded as `1` among the first 1000 SNPs are automatically identified as pseudohaploid. This leads to slightly more accurate estimates of f-statistics. Setting this parameter to `FALSE` treats all samples as diploid and is equivalent to the *ADMIXTOOLS* `inbreed: NO` option. Setting `adjust_pseudohaploid` to an integer `n` will check the first `n` SNPs instead of the first 1000 SNPs. +#' @param cols_per_chunk Number of allele frequency chunks to store on disk. Setting this to a positive integer makes the function slower, but requires less memory. The default value for `cols_per_chunk` in \code{\link{extract_afs}} is 10. Lower numbers will lower the memory requirement but increase the time it takes. +#' @param fst Write files with pairwise FST for every population pair. Setting this to FALSE can make `extract_f2` faster and will require less memory. +#' @param afprod Write files with allele frequency products for every population pair. Setting this to FALSE can make `extract_f2` faster and will require less memory. +#' @param poly_only Specify whether SNPs with identical allele frequencies in every population should be discarded (`poly_only = TRUE`), or whether they should be used (`poly_only = FALSE`). By default (`poly_only = c("f2")`), these SNPs will be used to compute FST and allele frequency products, but not to compute f2 (this is the default option in the original ADMIXTOOLS). +#' @param apply_corr Apply small-sample-size correction when computing f2-statistics (default `TRUE`) +#' @param n_cores Parallelize computation across `n_cores` cores via the `doParallel` package. +#' @param quiet Suppress printin of progress updates +#' @return SNP metadata (invisibly) +#' @export + +gt_extract_f2<- function(.x , outdir=NULL, blgsize = 0.05, maxmem = 8000, + maxmiss = 0, minmaf = 0, maxmaf = 0.5, minac2 = FALSE, outpop = NULL, outpop_scale = TRUE, + transitions = TRUE, transversions = TRUE, + auto_only = TRUE, keepsnps = NULL, overwrite = FALSE, format = NULL, + adjust_pseudohaploid = TRUE, cols_per_chunk = NULL, fst = TRUE, afprod = TRUE, + poly_only = c('f2'), apply_corr = TRUE, qpfstats = FALSE, n_cores = 1, quiet = FALSE) { + + inds = NULL + pops = NULL + pops2 = NULL + if (!is.null(cols_per_chunk)){ + stop("there is no option at the moment to split the processing in chunks") + } + if (!inherits(.x,"grouped_df")){ + stop (".x should be a grouped df") + } + # if no outdir is given, create a subdirectory f2 in the path of the gen_tibble rds + if (is.null(outdir)){ + outdir <- file.path(dirname(.gt_get_bigsnp(.x)$genotypes$rds),"f2") + } + + verbose <- !quiet + afdat = gt_to_aftable(.x) + + if(is.null(inds)) pops = union(pops, pops2) + afdat %<>% admixtools:::discard_from_aftable(maxmiss = maxmiss, minmaf = minmaf, maxmaf = maxmaf, minac2 = minac2, outpop = outpop, + transitions = transitions, transversions = transversions, + keepsnps = keepsnps, auto_only = auto_only, poly_only = FALSE) + afdat$snpfile %<>% mutate(poly = as.logical(admixtools:::cpp_is_polymorphic(afdat$afs))) + if(sum(afdat$snpfile$poly) == 0) stop('There are no informative SNPs!') + + if(verbose) message(paste0(nrow(afdat$afs), ' SNPs remain after filtering. ', + sum(afdat$snpfile$poly),' are polymorphic.\n')) + + if(isTRUE(poly_only)) poly_only = c('f2', 'ap', 'fst') + arrs = admixtools:::afs_to_f2_blocks(afdat, outdir = outdir, overwrite = overwrite, maxmem = maxmem, poly_only = poly_only, + pops1 = pops, pops2 = pops2, outpop = if(outpop_scale) outpop else NULL, + blgsize = blgsize, afprod = afprod, fst = fst, apply_corr = apply_corr, + n_cores = n_cores, verbose = verbose) + + if(is.null(outdir)) return(arrs) + + if(verbose) message(paste0('Data written to ', outdir, '/\n')) + invisible(afdat$snpfile) +} + + + +# admixtools equivalent functions gt_to_aftable <- function(.x, n_cores = bigstatsr::nb_cores()){ if (!inherits(.x,"grouped_df")){ stop (".x should be a grouped df") @@ -14,16 +95,9 @@ gt_to_aftable <- function(.x, n_cores = bigstatsr::nb_cores()){ names(aftable)<-c("afs","counts") .group_levels = .x %>% group_keys() %>% pull(1) - # summarise population frequencies - # pop_freqs_list <- group_map(.x, .f=~.gt_pop_freqs(.x)) - # afs <- do.call("cbind",lapply(pop_freqs_list,function(x)x[["freq_alt"]])) - # counts <- do.call("cbind",lapply(pop_freqs_list,function(x)x[["n"]])) - dimnames(aftable$afs)<-list(loci_names(.x),.group_levels) dimnames(aftable$counts)<-list(loci_names(.x),.group_levels) - - loci_new_names <- c(SNP = "name", CHR = "chromosome", POS="position",cm="genetic_dist", A1 = "allele_ref",A2 = "allele_alt") snp <- show_loci(.x) %>% select(-all_of("big_index")) %>% @@ -34,7 +108,69 @@ gt_to_aftable <- function(.x, n_cores = bigstatsr::nb_cores()){ return(aftable) } +########################################################################################### +# everything works up to here +# the function below needs to be modified to use gt_extract_afs + + +gt_extract_f2_large = function(pref, outdir, inds = NULL, pops = NULL, blgsize = 0.05, cols_per_chunk = 10, + maxmiss = 0, minmaf = 0, maxmaf = 0.5, minac2 = FALSE, outpop = NULL, outpop_scale = TRUE, + transitions = TRUE, transversions = TRUE, + keepsnps = NULL, snpblocks = NULL, overwrite = FALSE, format = NULL, + adjust_pseudohaploid = TRUE, afprod = TRUE, fst = TRUE, poly_only = c('f2'), + apply_corr = TRUE, verbose = TRUE) { + + if(verbose) alert_info(paste0('Extracting allele frequencies...\n')) + snpdat = extract_afs(pref, outdir, inds = inds, pops = pops, cols_per_chunk = cols_per_chunk, numparts = 100, + maxmiss = maxmiss, minmaf = minmaf, maxmaf = maxmaf, minac2 = minac2, outpop = outpop, + transitions = transitions, transversions = transversions, + keepsnps = keepsnps, format = format, poly_only = FALSE, + adjust_pseudohaploid = adjust_pseudohaploid, verbose = verbose) + + numchunks = length(list.files(outdir, 'afs.+rds')) + + if(!is.null(outpop) && outpop_scale) { + p = snpdat$outpopaf + snpwt = 1/(p*(1-p)) + } else snpwt = NULL + if(isTRUE(poly_only)) poly_only = c('f2', 'ap', 'fst') + + if(verbose) alert_warning(paste0('Computing ', choose(numchunks+1, 2), ' chunk pairs. If this takes too long, + consider running "extract_afs" and then paralellizing over "afs_to_f2".\n')) + for(i in 1:numchunks) { + for(j in i:numchunks) { + if(verbose) alert_info(paste0('Writing pair ', i, ' - ', j, '...\r')) + afs_to_f2(outdir, outdir, chunk1 = i, chunk2 = j, blgsize = blgsize, snpdat = snpdat, + snpwt = snpwt, overwrite = overwrite, type = 'f2', poly_only = 'f2' %in% poly_only, + apply_corr = apply_corr) + if(afprod) afs_to_f2(outdir, outdir, chunk1 = i, chunk2 = j, blgsize = blgsize, snpdat = snpdat, + snpwt = snpwt, overwrite = overwrite, type = 'ap', poly_only = 'ap' %in% poly_only) + if(fst) afs_to_f2(outdir, outdir, chunk1 = i, chunk2 = j, blgsize = blgsize, snpdat = snpdat, + snpwt = snpwt, overwrite = overwrite, type = 'fst', poly_only = 'fst' %in% poly_only) + } + } + if(verbose) alert_info('\n') + if(verbose) alert_info(paste0('Deleting allele frequency files...\n')) + unlink(paste0(outdir, c('/afs*.rds', '/counts*.rds'))) +} + + + + + + + + + + + + + + + + gt_extract_afs <- function(.x, outdir = "./afs", cols_per_chunk = 10, blgsize = 0.05, quiet=FALSE){ + warning ("this function is untested") if (!inherits(.x,"grouped_df")){ stop (".x should be a grouped df") } @@ -66,7 +202,7 @@ gt_extract_afs <- function(.x, outdir = "./afs", cols_per_chunk = 10, blgsize = invisible(afdat$snpfile) } -#' Compute adn store blocked f2 statistics for a `gen_tibble` +#' Compute and store blocked f2 statistics for a `gen_tibble` #' #' This function prepares data for various `ADMIXTOOLS 2` function, and it is #' equivalent to `admixtools::extract_f2`. An important difference is that @@ -85,13 +221,14 @@ gt_extract_afs <- function(.x, outdir = "./afs", cols_per_chunk = 10, blgsize = #' but increase the time it takes. #' @param quiet boolean on whether the progess should be silenced #' @returns SNP metadata (invisibly) -#' @export +#' @keywords internal -gt_extract_f2 <- function(.x , outdir=NULL, cols_per_chunk = 10, blgsize = 0.05, quiet=FALSE){ +## TO BE TESTED +gt_extract_f2_broken <- function(.x , outdir=NULL, cols_per_chunk = 10, blgsize = 0.05, quiet=FALSE){ + warning ("this function is untested") if (!inherits(.x,"grouped_df")){ stop (".x should be a grouped df") } - browser() # if no outdir is given, create a subdirectory f2 in the path of the gen_tibble rds if (is.null(outdir)){ outdir <- file.path(dirname(.gt_get_bigsnp(.x)$genotypes$rds),"f2") @@ -104,7 +241,6 @@ gt_extract_f2 <- function(.x , outdir=NULL, cols_per_chunk = 10, blgsize = 0.05, gt_extract_afs(.x, outdir = af_dir, cols_per_chunk = cols_per_chunk, blgsize = blgsize, quiet= quiet) numchunks = length(list.files(af_dir, 'afs.+rds')) - browser() for(i in 1:numchunks) { for(j in i:numchunks) { admixtools::afs_to_f2(af_dir, outdir, chunk1 = i, chunk2 = j) @@ -113,3 +249,6 @@ gt_extract_f2 <- function(.x , outdir=NULL, cols_per_chunk = 10, blgsize = 0.05, } #gt_extract_f2(test_gt) + + + diff --git a/R/utils-pipe.R b/R/utils-pipe.R index fd0b1d13..c1a45e24 100644 --- a/R/utils-pipe.R +++ b/R/utils-pipe.R @@ -7,6 +7,7 @@ #' @keywords internal #' @export #' @importFrom magrittr %>% +#' @importFrom magrittr %<>% #' @usage lhs \%>\% rhs #' @param lhs A value or the magrittr placeholder. #' @param rhs A function call using the magrittr semantics. diff --git a/man/gt_extract_f2.Rd b/man/gt_extract_f2.Rd index 78067dff..8a7ede3c 100644 --- a/man/gt_extract_f2.Rd +++ b/man/gt_extract_f2.Rd @@ -2,39 +2,89 @@ % Please edit documentation in R/gt_to_aftable.R \name{gt_extract_f2} \alias{gt_extract_f2} -\title{Compute adn store blocked f2 statistics for a \code{gen_tibble}} +\title{Compute and store blocked f2 statistics} \usage{ gt_extract_f2( .x, outdir = NULL, - cols_per_chunk = 10, blgsize = 0.05, + maxmem = 8000, + maxmiss = 0, + minmaf = 0, + maxmaf = 0.5, + minac2 = FALSE, + outpop = NULL, + outpop_scale = TRUE, + transitions = TRUE, + transversions = TRUE, + auto_only = TRUE, + keepsnps = NULL, + overwrite = FALSE, + format = NULL, + adjust_pseudohaploid = TRUE, + cols_per_chunk = NULL, + fst = TRUE, + afprod = TRUE, + poly_only = c("f2"), + apply_corr = TRUE, + qpfstats = FALSE, + n_cores = 1, quiet = FALSE ) } \arguments{ -\item{.x}{the \code{gen_tibble}, appropriately filtered for individuals, populations and snps} +\item{.x}{a \code{\link{gen_tibble}}} -\item{outdir}{the directory where the f2 stats will be stored. If left NULL, a directory -names 'f2' will be created in the same path as the RDS fo the \code{gen_tibble}} +\item{outdir}{Directory where data will be stored.} -\item{cols_per_chunk}{Number of allele frequency chunks to store on disk. -Setting this to a positive integer makes the function slower, -but requires less memory. The default value for cols_per_chunk -in extract_afs is 10. Lower numbers will lower the memory requirement -but increase the time it takes.} +\item{blgsize}{SNP block size in Morgan. Default is 0.05 (5 cM). If \code{blgsize} is 100 or greater, if will be interpreted as base pair distance rather than centimorgan distance.} -\item{blgsize}{SNP block size in Morgan. Default is 0.05 (5 cM). If \code{blgsize} -is 100 or greater, if will be interpreted as base pair distance rather than centimorgan distance.} +\item{maxmem}{Maximum amount of memory to be used. If the required amount of memory exceeds \code{maxmem}, allele frequency data will be split into blocks, and the computation will be performed separately on each block pair. This doesn't put a precise cap on the amount of memory used (it used to at some point). Set this parameter to lower values if you run out of memory while running this function. Set it to higher values if this function is too slow and you have lots of memory.} -\item{quiet}{boolean on whether the progess should be silenced} +\item{maxmiss}{Discard SNPs which are missing in a fraction of populations higher than \code{maxmiss}} + +\item{minmaf}{Discard SNPs with minor allele frequency less than \code{minmaf}} + +\item{maxmaf}{Discard SNPs with minor allele frequency greater than than \code{maxmaf}} + +\item{minac2}{Discard SNPs with allele count lower than 2 in any population (default \code{FALSE}). This option should be set to \code{TRUE} when computing f3-statistics where one population consists mostly of pseudohaploid samples. Otherwise heterozygosity estimates and thus f3-estimates can be biased. \code{minac2 == 2} will discard SNPs with allele count lower than 2 in any non-singleton population (this option is experimental and is based on the hypothesis that using SNPs with allele count lower than 2 only leads to biases in non-singleton populations). While the \code{minac2} option discards SNPs with allele count lower than 2 in any population, the \code{\link{qp3pop}} function will only discard SNPs with allele count lower than 2 in the first (target) population (when the first argument is the prefix of a genotype file).} + +\item{outpop}{Keep only SNPs which are heterozygous in this population} + +\item{outpop_scale}{Scale f2-statistics by the inverse \code{outpop} heteroygosity (\code{1/(p*(1-p))}). Providing \code{outpop} and setting \code{outpop_scale} to \code{TRUE} will give the same results as the original \emph{qpGraph} when the \code{outpop} parameter has been set, but it has the disadvantage of treating one population different from the others. This may limit the use of these f2-statistics for other models.} + +\item{transitions}{Set this to \code{FALSE} to exclude transition SNPs} + +\item{transversions}{Set this to \code{FALSE} to exclude transversion SNPs} + +\item{auto_only}{Keep only SNPs on chromosomes 1 to 22} + +\item{keepsnps}{SNP IDs of SNPs to keep. Overrides other SNP filtering options} + +\item{overwrite}{Overwrite existing files in \code{outdir}} + +\item{adjust_pseudohaploid}{Genotypes of pseudohaploid samples are usually coded as \code{0} or \code{2}, even though only one allele is observed. \code{adjust_pseudohaploid} ensures that the observed allele count increases only by \code{1} for each pseudohaploid sample. If \code{TRUE} (default), samples that don't have any genotypes coded as \code{1} among the first 1000 SNPs are automatically identified as pseudohaploid. This leads to slightly more accurate estimates of f-statistics. Setting this parameter to \code{FALSE} treats all samples as diploid and is equivalent to the \emph{ADMIXTOOLS} \code{inbreed: NO} option. Setting \code{adjust_pseudohaploid} to an integer \code{n} will check the first \code{n} SNPs instead of the first 1000 SNPs.} + +\item{cols_per_chunk}{Number of allele frequency chunks to store on disk. Setting this to a positive integer makes the function slower, but requires less memory. The default value for \code{cols_per_chunk} in \code{\link{extract_afs}} is 10. Lower numbers will lower the memory requirement but increase the time it takes.} + +\item{fst}{Write files with pairwise FST for every population pair. Setting this to FALSE can make \code{extract_f2} faster and will require less memory.} + +\item{afprod}{Write files with allele frequency products for every population pair. Setting this to FALSE can make \code{extract_f2} faster and will require less memory.} + +\item{poly_only}{Specify whether SNPs with identical allele frequencies in every population should be discarded (\code{poly_only = TRUE}), or whether they should be used (\code{poly_only = FALSE}). By default (\code{poly_only = c("f2")}), these SNPs will be used to compute FST and allele frequency products, but not to compute f2 (this is the default option in the original ADMIXTOOLS).} + +\item{apply_corr}{Apply small-sample-size correction when computing f2-statistics (default \code{TRUE})} + +\item{n_cores}{Parallelize computation across \code{n_cores} cores via the \code{doParallel} package.} + +\item{quiet}{Suppress printin of progress updates} } \value{ SNP metadata (invisibly) } \description{ -This function prepares data for various \verb{ADMIXTOOLS 2} function, and it is -equivalent to \code{admixtools::extract_f2}. An important difference is that -the filtering for snps (e.g. by maf) or populations is not performed by this -function; it is expected that the \code{gen_tibble} has been filtered appropriately. +This function prepares data for various other \emph{ADMIXTOOLS 2} functions. It +takes a \code{\link{gen_tibble}}, +computes allele frequencies and blocked f2-statistics for selected populations, +and writes the results to \code{outdir}. } diff --git a/man/gt_extract_f2_broken.Rd b/man/gt_extract_f2_broken.Rd new file mode 100644 index 00000000..5a61fe6b --- /dev/null +++ b/man/gt_extract_f2_broken.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/gt_to_aftable.R +\name{gt_extract_f2_broken} +\alias{gt_extract_f2_broken} +\title{Compute and store blocked f2 statistics for a \code{gen_tibble}} +\usage{ +gt_extract_f2_broken( + .x, + outdir = NULL, + cols_per_chunk = 10, + blgsize = 0.05, + quiet = FALSE +) +} +\arguments{ +\item{.x}{the \code{gen_tibble}, appropriately filtered for individuals, populations and snps} + +\item{outdir}{the directory where the f2 stats will be stored. If left NULL, a directory +names 'f2' will be created in the same path as the RDS fo the \code{gen_tibble}} + +\item{cols_per_chunk}{Number of allele frequency chunks to store on disk. +Setting this to a positive integer makes the function slower, +but requires less memory. The default value for cols_per_chunk +in extract_afs is 10. Lower numbers will lower the memory requirement +but increase the time it takes.} + +\item{blgsize}{SNP block size in Morgan. Default is 0.05 (5 cM). If \code{blgsize} +is 100 or greater, if will be interpreted as base pair distance rather than centimorgan distance.} + +\item{quiet}{boolean on whether the progess should be silenced} +} +\value{ +SNP metadata (invisibly) +} +\description{ +This function prepares data for various \verb{ADMIXTOOLS 2} function, and it is +equivalent to \code{admixtools::extract_f2}. An important difference is that +the filtering for snps (e.g. by maf) or populations is not performed by this +function; it is expected that the \code{gen_tibble} has been filtered appropriately. +} +\keyword{internal} From 05b786d54073d7f9a4087718e540d46d446190bc Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Fri, 17 May 2024 15:18:08 +0100 Subject: [PATCH 14/15] admixtools fixes and tests --- R/RcppExports.R | 4 + R/gt_extract_f2.R | 235 ++++++++++++++++++++++++ R/gt_to_aftable.R | 254 -------------------------- R/loci_missingness.R | 1 + R/show_genotypes.R | 2 +- R/show_loci.R | 2 +- data-raw/admixtools_temp.R | 139 ++++++++++++++ inst/extdata/ploidy_test.bk | Bin 240 -> 0 bytes inst/extdata/ploidy_test.rds | Bin 102472 -> 0 bytes man/gt_extract_f2.Rd | 95 +++++++--- man/gt_extract_f2_broken.Rd | 41 ----- man/gt_to_aftable.Rd | 31 ++++ man/identify_pseudohaploids.Rd | 22 +++ man/loci_missingness.Rd | 2 + man/show_genotypes.Rd | 6 +- src/RcppExports.cpp | 18 ++ src/gt_grouped_alt_freq_pseudohap.cpp | 49 +++++ tests/testthat/test_gt_extract_f2.R | 54 ++++++ vignettes/overview.Rmd | 17 +- 19 files changed, 633 insertions(+), 339 deletions(-) create mode 100644 R/gt_extract_f2.R delete mode 100644 R/gt_to_aftable.R create mode 100644 data-raw/admixtools_temp.R delete mode 100644 inst/extdata/ploidy_test.bk delete mode 100644 inst/extdata/ploidy_test.rds delete mode 100644 man/gt_extract_f2_broken.Rd create mode 100644 man/gt_to_aftable.Rd create mode 100644 man/identify_pseudohaploids.Rd create mode 100644 src/gt_grouped_alt_freq_pseudohap.cpp create mode 100644 tests/testthat/test_gt_extract_f2.R diff --git a/R/RcppExports.R b/R/RcppExports.R index 61e67f0e..57e983d5 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -5,6 +5,10 @@ gt_grouped_alt_freq_diploid <- function(BM, rowInd, colInd, groupIds, ngroups, n .Call(`_tidypopgen_gt_grouped_alt_freq_diploid`, BM, rowInd, colInd, groupIds, ngroups, ncores) } +gt_grouped_alt_freq_pseudohap <- function(BM, rowInd, colInd, groupIds, ngroups, ploidy, ncores) { + .Call(`_tidypopgen_gt_grouped_alt_freq_pseudohap`, BM, rowInd, colInd, groupIds, ngroups, ploidy, ncores) +} + gt_grouped_missingness <- function(BM, rowInd, colInd, groupIds, ngroups, ncores) { .Call(`_tidypopgen_gt_grouped_missingness`, BM, rowInd, colInd, groupIds, ngroups, ncores) } diff --git a/R/gt_extract_f2.R b/R/gt_extract_f2.R new file mode 100644 index 00000000..b1914e6e --- /dev/null +++ b/R/gt_extract_f2.R @@ -0,0 +1,235 @@ +#' Compute and store blocked f2 statistics for ADMIXTOOLS 2 +#' +#' This function prepares data for various *ADMIXTOOLS 2* functions fro the +#' package *ADMIXTOOLS 2*. It +#' takes a [`gen_tibble`], +#' computes allele frequencies and blocked f2-statistics, +#' and writes the results to `outdir`. It is equivalent to +#' `admixtools::extract_f2()`. +#' @param .x a [`gen_tibble`] +#' @param outdir Directory where data will be stored. +#' @param blgsize SNP block size in Morgan. Default is 0.05 (5 cM). If `blgsize` +#' is 100 or greater, if will be interpreted as base pair distance rather than +#' centimorgan distance. +#' @param maxmem Maximum amount of memory to be used. If the required amount +#' of memory exceeds `maxmem`, allele frequency data will be split into blocks, +#' and the computation will be performed separately on each block pair. +#' This doesn't put a precise cap on the amount of memory used (it used to +#' at some point). Set this parameter to lower values if you run out of memory +#' while running this function. Set it to higher values if this function is +#' too slow and you have lots of memory. +#' @param maxmiss Discard SNPs which are missing in a fraction of populations +#' higher than `maxmiss` +#' @param minmaf Discard SNPs with minor allele frequency less than `minmaf` +#' @param maxmaf Discard SNPs with minor allele frequency greater than +#' than `maxmaf` +#' @param minac2 Discard SNPs with allele count lower than 2 in any population +#' (default `FALSE`). This option should be set to `TRUE` when computing +#' f3-statistics where one population consists mostly of pseudohaploid samples. +#' Otherwise heterozygosity estimates and thus f3-estimates can be biased. +#' `minac2 == 2` will discard SNPs with allele count lower than 2 in any +#' non-singleton population (this option is experimental and is based on the +#' hypothesis that using SNPs with allele count lower than 2 only leads to +#' biases in non-singleton populations). Note that, While the `minac2` option +#' discards +#' SNPs with allele count lower than 2 in any population, the \code{qp3pop} +#' function will only discard SNPs with allele count lower than 2 in the +#' first (target) population (when the first argument is the prefix of +#' a genotype file; i.e. it is applied directly to a genotype file, not via +#' precomputing f2 from a [`gen_tibble`]). +#' @param outpop Keep only SNPs which are heterozygous in this population +#' @param outpop_scale Scale f2-statistics by the inverse `outpop` +#' heteroygosity (`1/(p*(1-p))`). Providing `outpop` and setting +#' `outpop_scale` to `TRUE` will give the same results as the original +#' *qpGraph* when the `outpop` parameter has been set, but it has the +#' disadvantage of treating one population different from the others. This +#' may limit the use of these f2-statistics for other models. +#' @param transitions Set this to `FALSE` to exclude transition SNPs +#' @param transversions Set this to `FALSE` to exclude transversion SNPs +#' @param overwrite Overwrite existing files in `outdir` +#' @param adjust_pseudohaploid Genotypes of pseudohaploid samples are +#' usually coded as `0` or `2`, even though only one allele is observed. +#' `adjust_pseudohaploid` ensures that the observed allele count increases +#' only by `1` for each pseudohaploid sample. If `TRUE` (default), samples +#' that don't have any genotypes coded as `1` among the first 1000 SNPs are +#' automatically identified as pseudohaploid. This leads to slightly more +#' accurate estimates of f-statistics. Setting this parameter to `FALSE` +#' treats all samples as diploid and is equivalent to the *ADMIXTOOLS* ` +#' inbreed: NO` option. Setting `adjust_pseudohaploid` to an integer `n` +#' will check the first `n` SNPs instead of the first 1000 SNPs. +#' @param fst Write files with pairwise FST for every population pair. +#' Setting this to FALSE can make `extract_f2` faster and will require less memory. +#' @param afprod Write files with allele frequency products for every +#' population pair. Setting this to FALSE can make `extract_f2` faster and +#' will require less memory. +#' @param poly_only Specify whether SNPs with identical allele frequencies in +#' every population should be discarded (`poly_only = TRUE`), or whether they +#' should be used (`poly_only = FALSE`). By default (`poly_only = c("f2")`), +#' these SNPs will be used to compute FST and allele frequency products, but +#' not to compute f2 (this is the default option in the original ADMIXTOOLS). +#' @param apply_corr Apply small-sample-size correction when computing +#' f2-statistics (default `TRUE`) +#' @param n_cores Parallelize computation across `n_cores` cores. +#' @param quiet Suppress printing of progress updates +#' @return SNP metadata (invisibly) +#' @export + +gt_extract_f2<- function(.x , outdir=NULL, blgsize = 0.05, maxmem = 8000, + maxmiss = 0, minmaf = 0, maxmaf = 0.5, minac2 = FALSE, outpop = NULL, outpop_scale = TRUE, + transitions = TRUE, transversions = TRUE, + overwrite = FALSE, + adjust_pseudohaploid = TRUE, fst = TRUE, afprod = TRUE, + poly_only = c('f2'), apply_corr = TRUE, n_cores = 1, quiet = FALSE) { + if (!requireNamespace("admixtools", quietly = TRUE)) { + stop( + "to use this function, first install package 'admixtools' with\n", + "devtools::install_github('uqrmaie1/admixtools')") + } + + # parameters that don't make sense with a gen_tibble + inds = NULL + pops = NULL + pops2 = NULL + auto_only = FALSE + keepsnps = NULL + + if (!inherits(.x,"grouped_df")){ + stop (".x should be a grouped df") + } + # if no outdir is given, create a subdirectory f2 in the path of the gen_tibble rds + if (is.null(outdir)){ + outdir <- file.path(dirname(.gt_get_bigsnp(.x)$genotypes$rds),"f2") + } + + verbose <- !quiet + afdat = gt_to_aftable(.x, + adjust_pseudohaploid = adjust_pseudohaploid, + n_cores = n_cores) + + if(is.null(inds)) pops = union(pops, pops2) + + afdat %<>% discard_from_aftable(maxmiss = maxmiss, minmaf = minmaf, maxmaf = maxmaf, minac2 = minac2, outpop = outpop, + transitions = transitions, transversions = transversions, + keepsnps = keepsnps, auto_only = auto_only, poly_only = FALSE) + afdat$snpfile %<>% mutate(poly = as.logical(cpp_is_polymorphic(afdat$afs))) + if(sum(afdat$snpfile$poly) == 0) stop('There are no informative SNPs!') + + if(verbose) message(paste0(nrow(afdat$afs), ' SNPs remain after filtering. ', + sum(afdat$snpfile$poly),' are polymorphic.\n')) + + if(isTRUE(poly_only)) poly_only = c('f2', 'ap', 'fst') + arrs = afs_to_f2_blocks(afdat, outdir = outdir, overwrite = overwrite, maxmem = maxmem, poly_only = poly_only, + pops1 = pops, pops2 = pops2, outpop = if(outpop_scale) outpop else NULL, + blgsize = blgsize, afprod = afprod, fst = fst, apply_corr = apply_corr, + n_cores = n_cores, verbose = verbose) + + if(is.null(outdir)) return(arrs) + + if(verbose) message(paste0('Data written to ', outdir, '/\n')) + invisible(afdat$snpfile) +} + + +#' Create an allele frequency table (aftable) for admixtools +#' +#' This function is equivalent to `anygeno_to_aftable()`, but the aftable is +#' created directly from the `gen_tibble`. +#' @param .x the [`gen_tibble`] +#' @param adjust_pseudohaploid Genotypes of pseudohaploid samples are +#' usually coded as `0` or `2`, even though only one allele is observed. +#' `adjust_pseudohaploid` ensures that the observed allele count increases +#' only by `1` for each pseudohaploid sample. If `TRUE` (default), samples +#' that don't have any genotypes coded as `1` among the first 1000 SNPs are +#' automatically identified as pseudohaploid. This leads to slightly more +#' accurate estimates of f-statistics. Setting this parameter to `FALSE` +#' treats all samples as diploid and is equivalent to the *ADMIXTOOLS* ` +#' inbreed: NO` option. Setting `adjust_pseudohaploid` to an integer `n` +#' will check the first `n` SNPs instead of the first 1000 SNPs. +#' @param n_cores Parallelize computation across `n_cores` cores. +#' @returns a list of 3 elements: afs, counts, and snp +#' @keywords internal + +gt_to_aftable <- function(.x, adjust_pseudohaploid= TRUE, n_cores = bigstatsr::nb_cores()){ + if (!inherits(.x,"grouped_df")){ + stop (".x should be a grouped df") + } + geno_fbm <- .gt_get_bigsnp(.x)$genotypes + + # TODO we need a version with mixed ploidy that allows to account for haploids (or pseudohaploids) + if (adjust_pseudohaploid){ + if (is.numeric(adjust_pseudohaploid)){ + n_test <- adjust_pseudohaploid + } else { + n_test <- 1000 + } + ploidy <- identify_pseudohaploids(.x, n_test) + aftable <- gt_grouped_alt_freq_pseudohap(BM = geno_fbm,rowInd = .gt_bigsnp_rows(.x), + colInd = .gt_bigsnp_cols(.x), + groupIds = dplyr::group_indices(.x)-1, + ngroups = max(dplyr::group_indices(.x)), + ploidy = ploidy, + ncores = n_cores) + + } else { + aftable <- gt_grouped_alt_freq_diploid(BM = geno_fbm,rowInd = .gt_bigsnp_rows(.x), + colInd = .gt_bigsnp_cols(.x), + groupIds = dplyr::group_indices(.x)-1, + ngroups = max(dplyr::group_indices(.x)), + ncores = n_cores) + + } + names(aftable)<-c("afs","counts") + + .group_levels = .x %>% group_keys() %>% pull(1) + dimnames(aftable$afs)<-list(loci_names(.x),.group_levels) + dimnames(aftable$counts)<-list(loci_names(.x),.group_levels) + + loci_new_names <- c(SNP = "name", CHR = "chromosome", POS="position",cm="genetic_dist", + A1 = "allele_alt",A2 = "allele_ref") + snp <- show_loci(.x) %>% select(-all_of("big_index")) %>% + rename(dplyr::all_of(loci_new_names)) %>% + dplyr::relocate(all_of(c("CHR", "SNP", "cm", "POS", "A1", "A2"))) + snp$A1[is.na(snp$A1)]<-"0" + class(snp)<-c("spec_tbl_df",class(snp)) + # we are missing column specs, which are generated by readr::col_spec + # do we need to make sure that CHR and SNP are character? + snp$CHR <- as.character(snp$CHR) + snp$SNP <- as.character(snp$SNP) + + aftable$snpfile <- snp + return(aftable) +} + + + + +# import some functions that are not exported by admixtools +discard_from_aftable <- function(...){ + utils::getFromNamespace("discard_from_aftable", "admixtools")(...)} + +afs_to_f2_blocks <- function(...){ + utils::getFromNamespace("afs_to_f2_blocks", "admixtools")(...)} + +cpp_is_polymorphic <- function(...) { + utils::getFromNamespace("cpp_is_polymorphic", "admixtools")(...)} + +#' Identify pseudohaploids +#' +#' Pseudohaploids are coded as all homozygotes; we find them by checking the +#' first 'n_test' loci and call a pseudohaploid if they have zero heterozygosity +#' (this is the same strategy employed in admixtools) +#' @param x the gen_tibble +#' @param n_test the number of loci being tested +#' @return a numeric vector of ploidy +#' @keywords internal +identify_pseudohaploids <- function (x, n_test = 1000){ + if (n_test>count_loci(x)){ + n_test <- count_loci(x) + } + sub_x <- select_loci(x,.sel_arg = dplyr::all_of(c(1:n_test))) %>% + dplyr::ungroup() + het_obs <- indiv_het_obs(sub_x) + ploidy <-rep(2,nrow(x)) + ploidy[het_obs==0]<-1 + return(ploidy) +} diff --git a/R/gt_to_aftable.R b/R/gt_to_aftable.R deleted file mode 100644 index 7398e651..00000000 --- a/R/gt_to_aftable.R +++ /dev/null @@ -1,254 +0,0 @@ -#' Compute and store blocked f2 statistics -#' -#' This function prepares data for various other *ADMIXTOOLS 2* functions. It -#' takes a [`gen_tibble`], -#' computes allele frequencies and blocked f2-statistics for selected populations, -#' and writes the results to `outdir`. -#' @export -#' @param .x a [`gen_tibble`] -#' @param outdir Directory where data will be stored. -#' @param blgsize SNP block size in Morgan. Default is 0.05 (5 cM). If `blgsize` is 100 or greater, if will be interpreted as base pair distance rather than centimorgan distance. -#' @param maxmem Maximum amount of memory to be used. If the required amount of memory exceeds `maxmem`, allele frequency data will be split into blocks, and the computation will be performed separately on each block pair. This doesn't put a precise cap on the amount of memory used (it used to at some point). Set this parameter to lower values if you run out of memory while running this function. Set it to higher values if this function is too slow and you have lots of memory. -#' @param maxmiss Discard SNPs which are missing in a fraction of populations higher than `maxmiss` -#' @param minmaf Discard SNPs with minor allele frequency less than `minmaf` -#' @param maxmaf Discard SNPs with minor allele frequency greater than than `maxmaf` -#' @param minac2 Discard SNPs with allele count lower than 2 in any population (default `FALSE`). This option should be set to `TRUE` when computing f3-statistics where one population consists mostly of pseudohaploid samples. Otherwise heterozygosity estimates and thus f3-estimates can be biased. `minac2 == 2` will discard SNPs with allele count lower than 2 in any non-singleton population (this option is experimental and is based on the hypothesis that using SNPs with allele count lower than 2 only leads to biases in non-singleton populations). While the `minac2` option discards SNPs with allele count lower than 2 in any population, the \code{\link{qp3pop}} function will only discard SNPs with allele count lower than 2 in the first (target) population (when the first argument is the prefix of a genotype file). -#' @param outpop Keep only SNPs which are heterozygous in this population -#' @param outpop_scale Scale f2-statistics by the inverse `outpop` heteroygosity (`1/(p*(1-p))`). Providing `outpop` and setting `outpop_scale` to `TRUE` will give the same results as the original *qpGraph* when the `outpop` parameter has been set, but it has the disadvantage of treating one population different from the others. This may limit the use of these f2-statistics for other models. -#' @param transitions Set this to `FALSE` to exclude transition SNPs -#' @param transversions Set this to `FALSE` to exclude transversion SNPs -#' @param auto_only Keep only SNPs on chromosomes 1 to 22 -#' @param keepsnps SNP IDs of SNPs to keep. Overrides other SNP filtering options -#' @param overwrite Overwrite existing files in `outdir` -#' @param adjust_pseudohaploid Genotypes of pseudohaploid samples are usually coded as `0` or `2`, even though only one allele is observed. `adjust_pseudohaploid` ensures that the observed allele count increases only by `1` for each pseudohaploid sample. If `TRUE` (default), samples that don't have any genotypes coded as `1` among the first 1000 SNPs are automatically identified as pseudohaploid. This leads to slightly more accurate estimates of f-statistics. Setting this parameter to `FALSE` treats all samples as diploid and is equivalent to the *ADMIXTOOLS* `inbreed: NO` option. Setting `adjust_pseudohaploid` to an integer `n` will check the first `n` SNPs instead of the first 1000 SNPs. -#' @param cols_per_chunk Number of allele frequency chunks to store on disk. Setting this to a positive integer makes the function slower, but requires less memory. The default value for `cols_per_chunk` in \code{\link{extract_afs}} is 10. Lower numbers will lower the memory requirement but increase the time it takes. -#' @param fst Write files with pairwise FST for every population pair. Setting this to FALSE can make `extract_f2` faster and will require less memory. -#' @param afprod Write files with allele frequency products for every population pair. Setting this to FALSE can make `extract_f2` faster and will require less memory. -#' @param poly_only Specify whether SNPs with identical allele frequencies in every population should be discarded (`poly_only = TRUE`), or whether they should be used (`poly_only = FALSE`). By default (`poly_only = c("f2")`), these SNPs will be used to compute FST and allele frequency products, but not to compute f2 (this is the default option in the original ADMIXTOOLS). -#' @param apply_corr Apply small-sample-size correction when computing f2-statistics (default `TRUE`) -#' @param n_cores Parallelize computation across `n_cores` cores via the `doParallel` package. -#' @param quiet Suppress printin of progress updates -#' @return SNP metadata (invisibly) -#' @export - -gt_extract_f2<- function(.x , outdir=NULL, blgsize = 0.05, maxmem = 8000, - maxmiss = 0, minmaf = 0, maxmaf = 0.5, minac2 = FALSE, outpop = NULL, outpop_scale = TRUE, - transitions = TRUE, transversions = TRUE, - auto_only = TRUE, keepsnps = NULL, overwrite = FALSE, format = NULL, - adjust_pseudohaploid = TRUE, cols_per_chunk = NULL, fst = TRUE, afprod = TRUE, - poly_only = c('f2'), apply_corr = TRUE, qpfstats = FALSE, n_cores = 1, quiet = FALSE) { - - inds = NULL - pops = NULL - pops2 = NULL - if (!is.null(cols_per_chunk)){ - stop("there is no option at the moment to split the processing in chunks") - } - if (!inherits(.x,"grouped_df")){ - stop (".x should be a grouped df") - } - # if no outdir is given, create a subdirectory f2 in the path of the gen_tibble rds - if (is.null(outdir)){ - outdir <- file.path(dirname(.gt_get_bigsnp(.x)$genotypes$rds),"f2") - } - - verbose <- !quiet - afdat = gt_to_aftable(.x) - - - if(is.null(inds)) pops = union(pops, pops2) - afdat %<>% admixtools:::discard_from_aftable(maxmiss = maxmiss, minmaf = minmaf, maxmaf = maxmaf, minac2 = minac2, outpop = outpop, - transitions = transitions, transversions = transversions, - keepsnps = keepsnps, auto_only = auto_only, poly_only = FALSE) - afdat$snpfile %<>% mutate(poly = as.logical(admixtools:::cpp_is_polymorphic(afdat$afs))) - if(sum(afdat$snpfile$poly) == 0) stop('There are no informative SNPs!') - - if(verbose) message(paste0(nrow(afdat$afs), ' SNPs remain after filtering. ', - sum(afdat$snpfile$poly),' are polymorphic.\n')) - - if(isTRUE(poly_only)) poly_only = c('f2', 'ap', 'fst') - arrs = admixtools:::afs_to_f2_blocks(afdat, outdir = outdir, overwrite = overwrite, maxmem = maxmem, poly_only = poly_only, - pops1 = pops, pops2 = pops2, outpop = if(outpop_scale) outpop else NULL, - blgsize = blgsize, afprod = afprod, fst = fst, apply_corr = apply_corr, - n_cores = n_cores, verbose = verbose) - - if(is.null(outdir)) return(arrs) - - if(verbose) message(paste0('Data written to ', outdir, '/\n')) - invisible(afdat$snpfile) -} - - - -# admixtools equivalent functions -gt_to_aftable <- function(.x, n_cores = bigstatsr::nb_cores()){ - if (!inherits(.x,"grouped_df")){ - stop (".x should be a grouped df") - } - geno_fbm <- .gt_get_bigsnp(.x)$genotypes - - aftable <- gt_grouped_alt_freq_diploid(BM = geno_fbm,rowInd = .gt_bigsnp_rows(.x), - colInd = .gt_bigsnp_cols(.x), - groupIds = dplyr::group_indices(.x)-1, - ngroups = max(dplyr::group_indices(.x)), - ncores = n_cores) - names(aftable)<-c("afs","counts") - - .group_levels = .x %>% group_keys() %>% pull(1) - dimnames(aftable$afs)<-list(loci_names(.x),.group_levels) - dimnames(aftable$counts)<-list(loci_names(.x),.group_levels) - - loci_new_names <- c(SNP = "name", CHR = "chromosome", POS="position",cm="genetic_dist", - A1 = "allele_ref",A2 = "allele_alt") - snp <- show_loci(.x) %>% select(-all_of("big_index")) %>% - rename(dplyr::all_of(loci_new_names)) %>% dplyr::relocate(all_of("cm"),.before="POS") - c("SNP","CHR","cm","POS","A1","A2") - - aftable$snpfile <- snp - return(aftable) -} - -########################################################################################### -# everything works up to here -# the function below needs to be modified to use gt_extract_afs - - -gt_extract_f2_large = function(pref, outdir, inds = NULL, pops = NULL, blgsize = 0.05, cols_per_chunk = 10, - maxmiss = 0, minmaf = 0, maxmaf = 0.5, minac2 = FALSE, outpop = NULL, outpop_scale = TRUE, - transitions = TRUE, transversions = TRUE, - keepsnps = NULL, snpblocks = NULL, overwrite = FALSE, format = NULL, - adjust_pseudohaploid = TRUE, afprod = TRUE, fst = TRUE, poly_only = c('f2'), - apply_corr = TRUE, verbose = TRUE) { - - if(verbose) alert_info(paste0('Extracting allele frequencies...\n')) - snpdat = extract_afs(pref, outdir, inds = inds, pops = pops, cols_per_chunk = cols_per_chunk, numparts = 100, - maxmiss = maxmiss, minmaf = minmaf, maxmaf = maxmaf, minac2 = minac2, outpop = outpop, - transitions = transitions, transversions = transversions, - keepsnps = keepsnps, format = format, poly_only = FALSE, - adjust_pseudohaploid = adjust_pseudohaploid, verbose = verbose) - - numchunks = length(list.files(outdir, 'afs.+rds')) - - if(!is.null(outpop) && outpop_scale) { - p = snpdat$outpopaf - snpwt = 1/(p*(1-p)) - } else snpwt = NULL - if(isTRUE(poly_only)) poly_only = c('f2', 'ap', 'fst') - - if(verbose) alert_warning(paste0('Computing ', choose(numchunks+1, 2), ' chunk pairs. If this takes too long, - consider running "extract_afs" and then paralellizing over "afs_to_f2".\n')) - for(i in 1:numchunks) { - for(j in i:numchunks) { - if(verbose) alert_info(paste0('Writing pair ', i, ' - ', j, '...\r')) - afs_to_f2(outdir, outdir, chunk1 = i, chunk2 = j, blgsize = blgsize, snpdat = snpdat, - snpwt = snpwt, overwrite = overwrite, type = 'f2', poly_only = 'f2' %in% poly_only, - apply_corr = apply_corr) - if(afprod) afs_to_f2(outdir, outdir, chunk1 = i, chunk2 = j, blgsize = blgsize, snpdat = snpdat, - snpwt = snpwt, overwrite = overwrite, type = 'ap', poly_only = 'ap' %in% poly_only) - if(fst) afs_to_f2(outdir, outdir, chunk1 = i, chunk2 = j, blgsize = blgsize, snpdat = snpdat, - snpwt = snpwt, overwrite = overwrite, type = 'fst', poly_only = 'fst' %in% poly_only) - } - } - if(verbose) alert_info('\n') - if(verbose) alert_info(paste0('Deleting allele frequency files...\n')) - unlink(paste0(outdir, c('/afs*.rds', '/counts*.rds'))) -} - - - - - - - - - - - - - - - - -gt_extract_afs <- function(.x, outdir = "./afs", cols_per_chunk = 10, blgsize = 0.05, quiet=FALSE){ - warning ("this function is untested") - if (!inherits(.x,"grouped_df")){ - stop (".x should be a grouped df") - } - if (!dir.exists(outdir)){ - dir.create(outdir) - } - verbose <- !quiet - afdat = gt_to_aftable(.x)#, inds = inds, pops = pops, - # format = format, adjust_pseudohaploid = adjust_pseudohaploid, - # verbose = verbose) -# afdat %<>% discard_from_aftable(maxmiss = maxmiss, minmaf = minmaf, -# maxmaf = maxmaf, minac2 = minac2, outpop = outpop, transitions = transitions, -# transversions = transversions, keepsnps = keepsnps, auto_only = TRUE, -# poly_only = poly_only) - afdat$snpfile <- afdat$snpfile %>% mutate(poly = as.logical(admixtools:::cpp_is_polymorphic(afdat$afs))) -# if (verbose) -# alert_warning(paste0(nrow(afdat$afs), " SNPs remain after filtering. ", -# sum(afdat$snpfile$poly), " are polymorphic.\n")) - admixtools::split_mat(afdat$afs, cols_per_chunk = cols_per_chunk, prefix = paste0(outdir, - "/afs"), verbose = verbose) - admixtools::split_mat(afdat$counts, cols_per_chunk = cols_per_chunk, - prefix = paste0(outdir, "/counts"), verbose = verbose) - block_lengths = admixtools::get_block_lengths(afdat$snpfile %>% filter(.data$poly), - blgsize = blgsize) - block_lengths_a = admixtools::get_block_lengths(afdat$snpfile, blgsize = blgsize) - saveRDS(block_lengths, file = paste0(outdir, "/block_lengths.rds")) - saveRDS(block_lengths_a, file = paste0(outdir, "/block_lengths_a.rds")) - readr::write_tsv(afdat$snpfile, paste0(outdir, "/snpdat.tsv.gz")) - invisible(afdat$snpfile) -} - -#' Compute and store blocked f2 statistics for a `gen_tibble` -#' -#' This function prepares data for various `ADMIXTOOLS 2` function, and it is -#' equivalent to `admixtools::extract_f2`. An important difference is that -#' the filtering for snps (e.g. by maf) or populations is not performed by this -#' function; it is expected that the `gen_tibble` has been filtered appropriately. -#' -#' @param .x the `gen_tibble`, appropriately filtered for individuals, populations and snps -#' @param outdir the directory where the f2 stats will be stored. If left NULL, a directory -#' names 'f2' will be created in the same path as the RDS fo the `gen_tibble` -#' @param blgsize SNP block size in Morgan. Default is 0.05 (5 cM). If `blgsize` -#' is 100 or greater, if will be interpreted as base pair distance rather than centimorgan distance. -#' @param cols_per_chunk Number of allele frequency chunks to store on disk. -#' Setting this to a positive integer makes the function slower, -#' but requires less memory. The default value for cols_per_chunk -#' in extract_afs is 10. Lower numbers will lower the memory requirement -#' but increase the time it takes. -#' @param quiet boolean on whether the progess should be silenced -#' @returns SNP metadata (invisibly) -#' @keywords internal - -## TO BE TESTED -gt_extract_f2_broken <- function(.x , outdir=NULL, cols_per_chunk = 10, blgsize = 0.05, quiet=FALSE){ - warning ("this function is untested") - if (!inherits(.x,"grouped_df")){ - stop (".x should be a grouped df") - } - # if no outdir is given, create a subdirectory f2 in the path of the gen_tibble rds - if (is.null(outdir)){ - outdir <- file.path(dirname(.gt_get_bigsnp(.x)$genotypes$rds),"f2") - } - # we will place the af tables into a subdirectory - af_dir <- file.path(outdir,"af_tbls") - if (!dir.exists(af_dir)){ - dir.create(af_dir,recursive = TRUE) - } - gt_extract_afs(.x, outdir = af_dir, cols_per_chunk = cols_per_chunk, - blgsize = blgsize, quiet= quiet) - numchunks = length(list.files(af_dir, 'afs.+rds')) - for(i in 1:numchunks) { - for(j in i:numchunks) { - admixtools::afs_to_f2(af_dir, outdir, chunk1 = i, chunk2 = j) - } - } -} - -#gt_extract_f2(test_gt) - - - diff --git a/R/loci_missingness.R b/R/loci_missingness.R index 7ca40873..4c154a79 100644 --- a/R/loci_missingness.R +++ b/R/loci_missingness.R @@ -7,6 +7,7 @@ #' or a [`gen_tibble`]. #' @param as_counts boolean defining whether the count of NAs (rather than the rate) #' should be returned. It defaults to FALSE (i.e. rates are returned by default). +#' @param n_cores number of cores to be used, it defaults to [bigstatsr::nb_cores()] #' @param ... other arguments passed to specific methods. #' @returns a vector of frequencies, one per locus #' @rdname loci_missingness diff --git a/R/show_genotypes.R b/R/show_genotypes.R index 31419fac..5501caaf 100644 --- a/R/show_genotypes.R +++ b/R/show_genotypes.R @@ -11,7 +11,7 @@ #' extract information on the alleles for those loci from a [`gen_tibble`]. #' @rdname show_genotypes #' @export -show_genotypes <- function(.x, ...) { +show_genotypes <- function(.x, indiv_indices=NULL, loci_indices=NULL, ...) { UseMethod("show_genotypes", .x) } diff --git a/R/show_loci.R b/R/show_loci.R index a013a9b2..0e73341c 100644 --- a/R/show_loci.R +++ b/R/show_loci.R @@ -18,7 +18,7 @@ show_loci <- function(.x, ...) { show_loci.tbl_df <- function(.x, ...){ stopifnot_gen_tibble(.x) # extract the column and hand it over to its method - show_loci(.x$genotypes) + show_loci(.x$genotypes, ...) } diff --git a/data-raw/admixtools_temp.R b/data-raw/admixtools_temp.R new file mode 100644 index 00000000..b22c6360 --- /dev/null +++ b/data-raw/admixtools_temp.R @@ -0,0 +1,139 @@ +########################################################################################### +# everything works up to here +# the function below needs to be modified to use gt_extract_afs + + +gt_extract_f2_large = function(x, outdir, blgsize = 0.05, cols_per_chunk = 10, + maxmiss = 0, minmaf = 0, maxmaf = 0.5, minac2 = FALSE, outpop = NULL, outpop_scale = TRUE, + transitions = TRUE, transversions = TRUE, + keepsnps = NULL, snpblocks = NULL, overwrite = FALSE, format = NULL, + adjust_pseudohaploid = TRUE, afprod = TRUE, fst = TRUE, poly_only = c('f2'), + apply_corr = TRUE, verbose = TRUE) { + inds = NULL + pops = NULL + if(!quiet) message(paste0('Extracting allele frequencies...\n')) + snpdat = extract_afs(pref, outdir, inds = inds, pops = pops, cols_per_chunk = cols_per_chunk, numparts = 100, + maxmiss = maxmiss, minmaf = minmaf, maxmaf = maxmaf, minac2 = minac2, outpop = outpop, + transitions = transitions, transversions = transversions, + keepsnps = keepsnps, format = format, poly_only = FALSE, + adjust_pseudohaploid = adjust_pseudohaploid, verbose = verbose) + + numchunks = length(list.files(outdir, 'afs.+rds')) + + if(!is.null(outpop) && outpop_scale) { + p = snpdat$outpopaf + snpwt = 1/(p*(1-p)) + } else snpwt = NULL + if(isTRUE(poly_only)) poly_only = c('f2', 'ap', 'fst') + + if(verbose) alert_warning(paste0('Computing ', choose(numchunks+1, 2), ' chunk pairs. If this takes too long, + consider running "extract_afs" and then paralellizing over "afs_to_f2".\n')) + for(i in 1:numchunks) { + for(j in i:numchunks) { + if(verbose) alert_info(paste0('Writing pair ', i, ' - ', j, '...\r')) + afs_to_f2(outdir, outdir, chunk1 = i, chunk2 = j, blgsize = blgsize, snpdat = snpdat, + snpwt = snpwt, overwrite = overwrite, type = 'f2', poly_only = 'f2' %in% poly_only, + apply_corr = apply_corr) + if(afprod) afs_to_f2(outdir, outdir, chunk1 = i, chunk2 = j, blgsize = blgsize, snpdat = snpdat, + snpwt = snpwt, overwrite = overwrite, type = 'ap', poly_only = 'ap' %in% poly_only) + if(fst) afs_to_f2(outdir, outdir, chunk1 = i, chunk2 = j, blgsize = blgsize, snpdat = snpdat, + snpwt = snpwt, overwrite = overwrite, type = 'fst', poly_only = 'fst' %in% poly_only) + } + } + if(verbose) alert_info('\n') + if(verbose) alert_info(paste0('Deleting allele frequency files...\n')) + unlink(paste0(outdir, c('/afs*.rds', '/counts*.rds'))) +} + + +## threre is something off with this function, ti does not seem to replicate extract_afs +gt_extract_afs <- function (.x, outdir = "./afs", cols_per_chunk = 10, + numparts = 100, maxmiss = 0, minmaf = 0, maxmaf = 0.5, minac2 = FALSE, + outpop = NULL, transitions = TRUE, transversions = TRUE, + format = NULL, poly_only = FALSE, adjust_pseudohaploid = TRUE, + quiet = FALSE){ + # variables that don't make sense with gen_tibble + inds = NULL + pops = NULL + auto_only = FALSE + keepsnps = NULL + + warning ("this function is untested") + if (!inherits(.x,"grouped_df")){ + stop (".x should be a grouped df") + } + if (!dir.exists(outdir)){ + dir.create(outdir) + } + verbose <- !quiet + afdat = gt_to_aftable(.x)#, inds = inds, pops = pops, + # format = format, adjust_pseudohaploid = adjust_pseudohaploid, + # verbose = verbose) + afdat %<>% discard_from_aftable(maxmiss = maxmiss, minmaf = minmaf, + maxmaf = maxmaf, minac2 = minac2, outpop = outpop, transitions = transitions, + transversions = transversions, keepsnps = keepsnps, auto_only = auto_only, + poly_only = poly_only) + afdat$snpfile <- afdat$snpfile %>% mutate(poly = as.logical(cpp_is_polymorphic(afdat$afs))) + if (!quiet) + message(paste0(nrow(afdat$afs), " SNPs remain after filtering. ", + sum(afdat$snpfile$poly), " are polymorphic.\n")) + admixtools::split_mat(afdat$afs, cols_per_chunk = cols_per_chunk, prefix = paste0(outdir, + "/afs"), verbose = verbose) + admixtools::split_mat(afdat$counts, cols_per_chunk = cols_per_chunk, + prefix = paste0(outdir, "/counts"), verbose = verbose) + block_lengths = admixtools::get_block_lengths(afdat$snpfile %>% filter(.data$poly), + blgsize = blgsize) + block_lengths_a = admixtools::get_block_lengths(afdat$snpfile, blgsize = blgsize) + saveRDS(block_lengths, file = paste0(outdir, "/block_lengths.rds")) + saveRDS(block_lengths_a, file = paste0(outdir, "/block_lengths_a.rds")) + readr::write_tsv(afdat$snpfile, paste0(outdir, "/snpdat.tsv.gz")) + invisible(afdat$snpfile) +} + +#' Compute and store blocked f2 statistics for a `gen_tibble` +#' +#' This function prepares data for various `ADMIXTOOLS 2` function, and it is +#' equivalent to `admixtools::extract_f2`. An important difference is that +#' the filtering for snps (e.g. by maf) or populations is not performed by this +#' function; it is expected that the `gen_tibble` has been filtered appropriately. +#' +#' @param .x the `gen_tibble`, appropriately filtered for individuals, populations and snps +#' @param outdir the directory where the f2 stats will be stored. If left NULL, a directory +#' names 'f2' will be created in the same path as the RDS fo the `gen_tibble` +#' @param blgsize SNP block size in Morgan. Default is 0.05 (5 cM). If `blgsize` +#' is 100 or greater, if will be interpreted as base pair distance rather than centimorgan distance. +#' @param cols_per_chunk Number of allele frequency chunks to store on disk. +#' Setting this to a positive integer makes the function slower, +#' but requires less memory. The default value for cols_per_chunk +#' in extract_afs is 10. Lower numbers will lower the memory requirement +#' but increase the time it takes. +#' @param quiet boolean on whether the progess should be silenced +#' @returns SNP metadata (invisibly) +#' @keywords internal + +## TO BE TESTED +gt_extract_f2_broken <- function(.x , outdir=NULL, cols_per_chunk = 10, blgsize = 0.05, quiet=FALSE){ + warning ("this function is untested") + if (!inherits(.x,"grouped_df")){ + stop (".x should be a grouped df") + } + # if no outdir is given, create a subdirectory f2 in the path of the gen_tibble rds + if (is.null(outdir)){ + outdir <- file.path(dirname(.gt_get_bigsnp(.x)$genotypes$rds),"f2") + } + # we will place the af tables into a subdirectory + af_dir <- file.path(outdir,"af_tbls") + if (!dir.exists(af_dir)){ + dir.create(af_dir,recursive = TRUE) + } + gt_extract_afs(.x, outdir = af_dir, cols_per_chunk = cols_per_chunk, + blgsize = blgsize, quiet= quiet) + numchunks = length(list.files(af_dir, 'afs.+rds')) + for(i in 1:numchunks) { + for(j in i:numchunks) { + admixtools::afs_to_f2(af_dir, outdir, chunk1 = i, chunk2 = j) + } + } +} + +#gt_extract_f2(test_gt) diff --git a/inst/extdata/ploidy_test.bk b/inst/extdata/ploidy_test.bk deleted file mode 100644 index 9fd8662c9ec1004609ce56ec81290f3ecc8a150b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240 zcmaiv!43d01Vgp#|9^QZbDC_81MMSW%YMQWppE%4S-S&)^fJ7X^yRVTh*0%RR%-xt OqNEqZF~SFq%?aKr?Es?y diff --git a/inst/extdata/ploidy_test.rds b/inst/extdata/ploidy_test.rds deleted file mode 100644 index 944e7ce35846f5676a4950edc9865b2ef9cf9f5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102472 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

  • r-&Rb&ouuDR+G>{r@5DU2 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 gt_grouped_alt_freq_pseudohap(Environment BM, const IntegerVector& rowInd, const IntegerVector& colInd, const IntegerVector& groupIds, int ngroups, const IntegerVector& ploidy, int ncores); +RcppExport SEXP _tidypopgen_gt_grouped_alt_freq_pseudohap(SEXP BMSEXP, SEXP rowIndSEXP, SEXP colIndSEXP, SEXP groupIdsSEXP, SEXP ngroupsSEXP, SEXP ploidySEXP, SEXP ncoresSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< Environment >::type BM(BMSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type rowInd(rowIndSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type colInd(colIndSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type groupIds(groupIdsSEXP); + Rcpp::traits::input_parameter< int >::type ngroups(ngroupsSEXP); + Rcpp::traits::input_parameter< const IntegerVector& >::type ploidy(ploidySEXP); + Rcpp::traits::input_parameter< int >::type ncores(ncoresSEXP); + rcpp_result_gen = Rcpp::wrap(gt_grouped_alt_freq_pseudohap(BM, rowInd, colInd, groupIds, ngroups, ploidy, ncores)); + return rcpp_result_gen; +END_RCPP +} // gt_grouped_missingness NumericMatrix gt_grouped_missingness(Environment BM, const IntegerVector& rowInd, const IntegerVector& colInd, const IntegerVector& groupIds, int ngroups, int ncores); RcppExport SEXP _tidypopgen_gt_grouped_missingness(SEXP BMSEXP, SEXP rowIndSEXP, SEXP colIndSEXP, SEXP groupIdsSEXP, SEXP ngroupsSEXP, SEXP ncoresSEXP) { @@ -155,6 +172,7 @@ END_RCPP static const R_CallMethodDef CallEntries[] = { {"_tidypopgen_gt_grouped_alt_freq_diploid", (DL_FUNC) &_tidypopgen_gt_grouped_alt_freq_diploid, 6}, + {"_tidypopgen_gt_grouped_alt_freq_pseudohap", (DL_FUNC) &_tidypopgen_gt_grouped_alt_freq_pseudohap, 7}, {"_tidypopgen_gt_grouped_missingness", (DL_FUNC) &_tidypopgen_gt_grouped_missingness, 6}, {"_tidypopgen_gt_grouped_summaries", (DL_FUNC) &_tidypopgen_gt_grouped_summaries, 6}, {"_tidypopgen_SNPHWE2", (DL_FUNC) &_tidypopgen_SNPHWE2, 4}, diff --git a/src/gt_grouped_alt_freq_pseudohap.cpp b/src/gt_grouped_alt_freq_pseudohap.cpp new file mode 100644 index 00000000..e4a1dcf0 --- /dev/null +++ b/src/gt_grouped_alt_freq_pseudohap.cpp @@ -0,0 +1,49 @@ +/******************************************************************************/ + +#include + +/******************************************************************************/ + +// [[Rcpp::export]] +ListOf gt_grouped_alt_freq_pseudohap(Environment BM, + const IntegerVector& rowInd, + const IntegerVector& colInd, + const IntegerVector& groupIds, + int ngroups, + const IntegerVector& ploidy, + int ncores) { + + XPtr xpBM = BM["address"]; + SubBMCode256Acc macc(xpBM, rowInd, colInd, BM["code256"], 1); + + size_t n = macc.nrow(); // number of individuals + size_t m = macc.ncol(); // number of loci + + NumericMatrix freq(m, ngroups); + NumericMatrix valid_alleles(m, ngroups); + +#pragma omp parallel for num_threads(ncores) + for (size_t j = 0; j < m; j++) { + for (size_t i = 0; i < n; i++) { + double x = macc(i, j); + if (x>-1){ + if (ploidy[i]==2){ + freq(j, groupIds[i]) += x; + valid_alleles(j, groupIds[i]) +=2; + } else { + freq(j, groupIds[i]) += x/2; + valid_alleles(j, groupIds[i]) +=1; + } + } + } + // now for each group, divide freq by valid_alleles + for (size_t group_i = 0; group_i < ngroups; group_i++) { + freq(j, group_i) = freq(j, group_i) / valid_alleles(j, group_i); + } + } + + return List::create(_["freq_alt"] = freq, + _["n"] = valid_alleles); +} + +/******************************************************************************/ diff --git a/tests/testthat/test_gt_extract_f2.R b/tests/testthat/test_gt_extract_f2.R new file mode 100644 index 00000000..69f09113 --- /dev/null +++ b/tests/testthat/test_gt_extract_f2.R @@ -0,0 +1,54 @@ +test_genotypes <- rbind(c(1,1,0,1,1,0), + c(2,1,0,NA,0,0), + c(2,NA,0,0,1,1), + c(2,0,0,2,0,0), + c(1,2,0,1,2,1), + c(0,0,0,0,NA,2), + c(0,1,2,0,1,NA)) +test_indiv_meta <- data.frame (id=c("a","b","c","d","e","f","g"), + population = c("pop1","pop1","pop2","pop2","pop1","pop3","pop3")) +test_loci <- data.frame(name=paste0("rs",1:6), + chromosome=paste0("chr",c(1,1,1,1,2,2)), + position=as.integer(c(3,5,65,343,23,456)), + genetic_dist = as.integer(rep(0,6)), + allele_ref = c("A","T","C","G","C","T"), + allele_alt = c("T","C", NA,"C","G","A")) + +test_gt <- gen_tibble(x = test_genotypes, + loci = test_loci, + indiv_meta = test_indiv_meta, + quiet = TRUE) +test_gt <- test_gt %>% group_by(population) + +test_that("extract f2 correctly",{ + # process the data with admixtools (note that we get some warnings) + bed_file <- gt_as_plink(test_gt, file = tempfile("test_bed")) + + # test af table + # without adjusting pseudohaploids + adm_aftable <- admixtools:::anygeno_to_aftable(bigsnpr::sub_bed(bed_file), + adjust_pseudohaploid = FALSE) + gt_aftable <- gt_to_aftable(test_gt, adjust_pseudohaploid = FALSE) + expect_true(all.equal(adm_aftable, gt_aftable, check.attributes= FALSE)) + # now adjusting the pseudohaploids + adm_aftable <- admixtools:::anygeno_to_aftable(bigsnpr::sub_bed(bed_file), + adjust_pseudohaploid = TRUE) + gt_aftable <- gt_to_aftable(test_gt) + expect_true(all.equal(adm_aftable, gt_aftable, check.attributes= FALSE)) + + adm_outdir <- file.path(tempdir(),"adm_f2") + unlink(file.path(adm_outdir,"*"),recursive = TRUE) + # we get a few warnings due to the small sample size + suppressWarnings(admixtools::extract_f2(bigsnpr::sub_bed(bed_file), outdir = adm_outdir)) + expect_warning(adm_f2 <- admixtools::f2_from_precomp(adm_outdir)) + # now try to do the same with gen_tibble + gt_outdir <- file.path(tempdir(),"gt_f2") + unlink(file.path(gt_outdir,"*"),recursive = TRUE) + # we get same warning due to the small dataset + # TODO can we capture the warnings above and then check that they are the same?!? + suppressWarnings(gt_extract_f2(test_gt, outdir = gt_outdir)) + expect_warning(gt_f2 <- admixtools::f2_from_precomp(adm_outdir)) + expect_true(all.equal(adm_f2, gt_f2)) + + +}) diff --git a/vignettes/overview.Rmd b/vignettes/overview.Rmd index 7d8e3925..2a4ab234 100644 --- a/vignettes/overview.Rmd +++ b/vignettes/overview.Rmd @@ -31,12 +31,12 @@ be unique for each individual), `population` giving the population the individuals belong to (a `factor`), and `genotypes` (stored in a compressed format as a File-Backed Matrix, with the vector in the tibble providing the row indices of the matrix for each individual). The real data reside on disk, and an attribute `bigsnp` of -the `genotype` column contains all the information to acess it. There is also an +the `genotype` column contains all the information to access it. There is also an additional attribute , `loci` which provides all the information about the loci, including the column indices that represent in each locu in the FBM. The vector of row indices and the table of loci can be subsetted and reordered without changing the data on disk; thus, any operation on the `gen_tibble` is fast as it -shapes the indices of the genotyp matrix rather than the matrix itself. +shapes the indices of the genotype matrix rather than the matrix itself. The `loci` tibble includes columns `big_index` for the index in the FBM, `name` for the locus name (a `character`, which must be unique), `chromosome` for the chromosome (`a factor`, if known, otherwise set to `NA`), `position` for the position on the chromosome (`numeric`, if known, @@ -49,13 +49,12 @@ centimorgans) can be added as columns in the `loci` attribute table. [the class type of each column needs checking!!!] In principle, it is possible to use use multiple ways to compress the genotypes. -`tidypopgen` currently uses `bigSNP` from the package `bigsnpr`. It is very -fast and well documented, but it is mostly geared towards diploid data. However, -with a bit of work it could be expanded to multiple ploidy. NOte that the -infrastructure is present to adopt alternative compression approaches in the future -the only constraint is that the data have to be representable as a list of n elements -where n is the number of individuals (i.e. one object per individual). For example, -we could keep the data in memory, and have a `SNPBin` object in each element of the list. +`tidypopgen` currently uses a `bigSNP` object from the package `bigsnpr`. It is very +fast and well documented, but it is mostly geared towards diploid data. `tidypopgen` +expands that object to deal with different levels of ploidy, including multiple ploidy +within a single dataset; however, most functions are currently incompatible with +ploidy levels other than 2 (but they will return a clear error message and avoid +computing anything incorrectly). ## The grammar of population genetics From 57bf196e52afe643174cf32992c6d3f15999a812 Mon Sep 17 00:00:00 2001 From: Andrea Manica Date: Fri, 17 May 2024 15:23:06 +0100 Subject: [PATCH 15/15] bump version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index beeec759..14eb5403 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: tidypopgen Title: Tidy Population Genetics -Version: 0.0.0.9011 +Version: 0.0.0.9012 Authors@R: c(person("Andrea", "Manica", email = "am315@cam.ac.uk", role = c("aut", "cre"),