diff -Nru plink2-2.00~a3-200116+dfsg/cindex/DESCRIPTION plink2-2.00~a3-200217+dfsg/cindex/DESCRIPTION --- plink2-2.00~a3-200116+dfsg/cindex/DESCRIPTION 2019-12-14 01:28:59.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/cindex/DESCRIPTION 2020-01-19 18:57:17.000000000 +0000 @@ -1,8 +1,8 @@ Package: cindex Type: Package Title: C-index -Version: 0.1 -Date: 2019-12-12 +Version: 0.2 +Date: 2020-01-19 Author: Christopher Chang Maintainer: Christopher Chang Description: Supports fast concordance-index computation. diff -Nru plink2-2.00~a3-200116+dfsg/cindex/R/old.R plink2-2.00~a3-200217+dfsg/cindex/R/old.R --- plink2-2.00~a3-200116+dfsg/cindex/R/old.R 2019-12-14 01:28:59.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/cindex/R/old.R 2020-01-20 17:30:15.000000000 +0000 @@ -15,7 +15,7 @@ i=riskset rest=which(y>y[i]) if(length(rest)>0){ - ww=(w[rest]+w[i])/2 + ww=w[rest]*w[i] total=sum(ww) concordant = 1.0*sum(ww*(yhat[rest] do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 -CIndex <- function(yhat, y, status) { - .Call(`_cindex_CIndex`, yhat, y, status) +CIndex <- function(yhat, y, status, w = NULL) { + .Call(`_cindex_CIndex`, yhat, y, status, w) } diff -Nru plink2-2.00~a3-200116+dfsg/cindex/src/cindex.cpp plink2-2.00~a3-200217+dfsg/cindex/src/cindex.cpp --- plink2-2.00~a3-200116+dfsg/cindex/src/cindex.cpp 2019-12-16 16:22:48.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/cindex/src/cindex.cpp 2020-01-20 20:39:15.000000000 +0000 @@ -13,12 +13,14 @@ } } CIndexRec; -uint32_t PopcountLookup(const uintptr_t* new_yhat_present, const uint16_t* new_yhat_64b_popcounts, const uint16_t* new_yhat_2048b_popcounts, const uint32_t* new_yhat_64kib_popcounts, uint32_t widx); +uint32_t PopcountLookup(const uintptr_t* new_yhat_present, const uint16_t* new_yhat_64b_popcounts, const uint16_t* new_yhat_2kib_popcounts, const uint32_t* new_yhat_64kib_popcounts, uint32_t widx); -double CIndexTieheavyMain(const CIndexRec* recs, uintptr_t size, unsigned char* wkspace, uintptr_t* new_yhat_present, uint16_t* new_yhat_64b_popcounts, uint16_t* new_yhat_2048b_popcounts, uint32_t* new_yhat_64kib_popcounts); +double CIndexTieheavyMain(const CIndexRec* recs, uintptr_t size, unsigned char* wkspace, uintptr_t* new_yhat_present, uint16_t* new_yhat_64b_popcounts, uint16_t* new_yhat_2kib_popcounts, uint32_t* new_yhat_64kib_popcounts); + +double CIndexWeighted(NumericVector yhat, NumericVector y, SEXP status, const double* weightsd, uintptr_t orig_size); // [[Rcpp::export]] -double CIndex(NumericVector yhat, NumericVector y, SEXP status) { +double CIndex(NumericVector yhat, NumericVector y, SEXP status, Nullable w = R_NilValue) { if ((TYPEOF(status) != INTSXP) && (TYPEOF(status) != REALSXP)) { // could support LGLSXP too stop("Unsupported status type"); @@ -30,12 +32,26 @@ if (size != static_cast(y.size())) { stop("yhat and y must have the same length"); } + if (w.isNotNull()) { + NumericVector weights = w.get(); + if (size != static_cast(weights.size())) { + stop("w must have the same length as yhat and y"); + } + // May as well fall through if all weights are equal. + const double* weightsd = &(weights[0]); + const double first_wt = weightsd[0]; + for (uintptr_t ulii = 1; ulii != size; ++ulii) { + if (first_wt != weightsd[ulii]) { + return CIndexWeighted(yhat, y, status, weightsd, size); + } + } + } - // We define C-index as: - // sum_{all k where status[k]=1} [#(m: y[m]>y[j], yhat[m]y[j], yhat[m]y[j], yhat[m]==yhat[j])] // ------------------------------------------------------------------------ - // sum_{all k where status[k]=1} #(m: y[m]>y[j]) + // sum_{all j where status[j]=1} #(m: y[m]>y[j]) // // The computation is organized as follows: // 1. Replace yhat with 0-based ranks ('yhat_int'); note that this has no @@ -60,17 +76,17 @@ // - recs: N CIndexRec // - new_yhat_present: N bits // - new_yhat_64b_popcounts: DivUp(N, 512) uint16s - // - new_yhat_2048b_popcounts: DivUp(N, 512*32) uint16s + // - new_yhat_2kib_popcounts: DivUp(N, 512*32) uint16s // - new_yhat_64kib_popcounts: DivUp(N, 512*32*32) uint16s const uintptr_t recs_vec_ct = plink2::DivUp(sizeof(CIndexRec) * size, plink2::kBytesPerVec); const uintptr_t new_yhat_present_cacheline_ct = plink2::DivUp(size, plink2::kBitsPerCacheline); const uintptr_t new_yhat_present_vec_ct = new_yhat_present_cacheline_ct * plink2::kVecsPerCacheline; const uintptr_t new_yhat_64b_popcounts_cacheline_ct = plink2::DivUp(new_yhat_present_cacheline_ct, plink2::kInt16PerCacheline); const uintptr_t new_yhat_64b_popcounts_vec_ct = new_yhat_64b_popcounts_cacheline_ct * plink2::kVecsPerCacheline; - const uintptr_t new_yhat_2048b_popcounts_cacheline_ct = plink2::DivUp(new_yhat_64b_popcounts_cacheline_ct, plink2::kInt16PerCacheline); - const uintptr_t new_yhat_2048b_popcounts_vec_ct = new_yhat_2048b_popcounts_cacheline_ct * plink2::kVecsPerCacheline; - const uintptr_t new_yhat_64kib_popcounts_vec_ct = plink2::DivUp(new_yhat_2048b_popcounts_cacheline_ct, plink2::kInt32PerVec); - const uintptr_t vec_ct = recs_vec_ct + new_yhat_present_vec_ct + new_yhat_64b_popcounts_vec_ct + new_yhat_2048b_popcounts_vec_ct + new_yhat_64kib_popcounts_vec_ct; + const uintptr_t new_yhat_2kib_popcounts_cacheline_ct = plink2::DivUp(new_yhat_64b_popcounts_cacheline_ct, plink2::kInt16PerCacheline); + const uintptr_t new_yhat_2kib_popcounts_vec_ct = new_yhat_2kib_popcounts_cacheline_ct * plink2::kVecsPerCacheline; + const uintptr_t new_yhat_64kib_popcounts_vec_ct = plink2::DivUp(new_yhat_2kib_popcounts_cacheline_ct, plink2::kInt32PerVec); + const uintptr_t vec_ct = recs_vec_ct + new_yhat_present_vec_ct + new_yhat_64b_popcounts_vec_ct + new_yhat_2kib_popcounts_vec_ct + new_yhat_64kib_popcounts_vec_ct; unsigned char* wkspace; if (plink2::cachealigned_malloc(vec_ct * plink2::kBytesPerVec, &wkspace)) { stop("Out of memory"); @@ -83,8 +99,8 @@ wkspace_iter = &(wkspace_iter[new_yhat_present_vec_ct * plink2::kBytesPerVec]); uint16_t* new_yhat_64b_popcounts = reinterpret_cast(wkspace_iter); wkspace_iter = &(wkspace_iter[new_yhat_64b_popcounts_vec_ct * plink2::kBytesPerVec]); - uint16_t* new_yhat_2048b_popcounts = reinterpret_cast(wkspace_iter); - wkspace_iter = &(wkspace_iter[new_yhat_2048b_popcounts_vec_ct * plink2::kBytesPerVec]); + uint16_t* new_yhat_2kib_popcounts = reinterpret_cast(wkspace_iter); + wkspace_iter = &(wkspace_iter[new_yhat_2kib_popcounts_vec_ct * plink2::kBytesPerVec]); uint32_t* new_yhat_64kib_popcounts = reinterpret_cast(wkspace_iter); wkspace_iter = &(wkspace_iter[new_yhat_64kib_popcounts_vec_ct * plink2::kBytesPerVec]); CIndexRec* recs = reinterpret_cast(wkspace_iter); @@ -158,7 +174,7 @@ // sequential scans in case of ties. const uint64_t tie_burden = (static_cast(size) * (size - 1)) / 2 - yhat_int_sum; if (tie_burden > 512 * static_cast(size)) { - return CIndexTieheavyMain(recs, size, wkspace, new_yhat_present, new_yhat_64b_popcounts, new_yhat_2048b_popcounts, new_yhat_64kib_popcounts); + return CIndexTieheavyMain(recs, size, wkspace, new_yhat_present, new_yhat_64b_popcounts, new_yhat_2kib_popcounts, new_yhat_64kib_popcounts); } // Main loop. @@ -202,7 +218,7 @@ } } new_yhat_64b_popcounts[widx / plink2::kWordsPerCacheline] += 1; - new_yhat_2048b_popcounts[widx / (plink2::kWordsPerCacheline * plink2::kInt16PerCacheline)] += 1; + new_yhat_2kib_popcounts[widx / (plink2::kWordsPerCacheline * plink2::kInt16PerCacheline)] += 1; new_yhat_64kib_popcounts[widx / (plink2::kWordsPerCacheline * plink2::kInt16PerCacheline * plink2::kInt16PerCacheline)] += 1; } recs_block_end = next_block_end; @@ -216,7 +232,7 @@ const uint32_t yhat_int = recs[recs_idx].yhat_int; // numer_x2 += 2 * <# of set bits before yhat_int> + <# of ties> uint32_t widx = yhat_int / plink2::kBitsPerWord; - uint32_t n_set_bits_before = PopcountLookup(new_yhat_present, new_yhat_64b_popcounts, new_yhat_2048b_popcounts, new_yhat_64kib_popcounts, widx); + uint32_t n_set_bits_before = PopcountLookup(new_yhat_present, new_yhat_64b_popcounts, new_yhat_2kib_popcounts, new_yhat_64kib_popcounts, widx); uint32_t remainder = yhat_int % plink2::kBitsPerWord; uintptr_t cur_word = new_yhat_present[widx]; uintptr_t remainder_bit = plink2::k1LU << remainder; @@ -244,15 +260,15 @@ return plink2::u63tod(numer_x2) / plink2::u63tod(denom * 2); } -uint32_t PopcountLookup(const uintptr_t* new_yhat_present, const uint16_t* new_yhat_64b_popcounts, const uint16_t* new_yhat_2048b_popcounts, const uint32_t* new_yhat_64kib_popcounts, uint32_t widx) { +uint32_t PopcountLookup(const uintptr_t* new_yhat_present, const uint16_t* new_yhat_64b_popcounts, const uint16_t* new_yhat_2kib_popcounts, const uint32_t* new_yhat_64kib_popcounts, uint32_t widx) { uint32_t result = 0; const uint32_t idx_64kib = widx / (plink2::kWordsPerCacheline * plink2::kInt16PerCacheline * plink2::kInt16PerCacheline); for (uint32_t uii = 0; uii != idx_64kib; ++uii) { result += new_yhat_64kib_popcounts[uii]; } - const uint32_t idx_2048b = widx / (plink2::kWordsPerCacheline * plink2::kInt16PerCacheline); - for (uint32_t uii = idx_64kib * plink2::kInt16PerCacheline; uii != idx_2048b; ++uii) { - result += new_yhat_2048b_popcounts[uii]; + const uint32_t idx_2kib = widx / (plink2::kWordsPerCacheline * plink2::kInt16PerCacheline); + for (uint32_t uii = idx_64kib * plink2::kInt16PerCacheline; uii != idx_2kib; ++uii) { + result += new_yhat_2kib_popcounts[uii]; } // Each entry in the bottom-level-index array is <= 512, and we're adding @@ -262,7 +278,7 @@ const uintptr_t* walias_64b_popcounts = reinterpret_cast(new_yhat_64b_popcounts); const uint32_t widx_64b = widx / (plink2::kWordsPerCacheline * plink2::kInt16PerWord); uintptr_t acc = 0; - for (uint32_t uii = idx_2048b * plink2::kWordsPerCacheline; uii != widx_64b; ++uii) { + for (uint32_t uii = idx_2kib * plink2::kWordsPerCacheline; uii != widx_64b; ++uii) { acc += walias_64b_popcounts[uii]; } const uint32_t idx_64b = widx / plink2::kWordsPerCacheline; @@ -278,7 +294,7 @@ return result; } -double CIndexTieheavyMain(const CIndexRec* recs, uintptr_t size, unsigned char* wkspace, uintptr_t* new_yhat_present, uint16_t* new_yhat_64b_popcounts, uint16_t* new_yhat_2048b_popcounts, uint32_t* new_yhat_64kib_popcounts) { +double CIndexTieheavyMain(const CIndexRec* recs, uintptr_t size, unsigned char* wkspace, uintptr_t* new_yhat_present, uint16_t* new_yhat_64b_popcounts, uint16_t* new_yhat_2kib_popcounts, uint32_t* new_yhat_64kib_popcounts) { // Alternate tie-handling strategy: maintain an additional array which tracks // the number of times we've seen each yhat_int value. uint32_t* yhat_int_multiplicities = static_cast(calloc(size, sizeof(int32_t))); @@ -306,7 +322,7 @@ const uintptr_t remainder_bit = plink2::k1LU << (adj_yhat_int % plink2::kBitsPerWord); new_yhat_present[widx] |= remainder_bit; new_yhat_64b_popcounts[widx / plink2::kWordsPerCacheline] += 1; - new_yhat_2048b_popcounts[widx / (plink2::kWordsPerCacheline * plink2::kInt16PerCacheline)] += 1; + new_yhat_2kib_popcounts[widx / (plink2::kWordsPerCacheline * plink2::kInt16PerCacheline)] += 1; new_yhat_64kib_popcounts[widx / (plink2::kWordsPerCacheline * plink2::kInt16PerCacheline * plink2::kInt16PerCacheline)] += 1; } recs_block_end = next_block_end; @@ -319,7 +335,7 @@ denom += new_yhat_entry_ct; const uint32_t yhat_int = recs[recs_idx].yhat_int; uint32_t widx = yhat_int / plink2::kBitsPerWord; - uint32_t n_set_bits_before = PopcountLookup(new_yhat_present, new_yhat_64b_popcounts, new_yhat_2048b_popcounts, new_yhat_64kib_popcounts, widx); + uint32_t n_set_bits_before = PopcountLookup(new_yhat_present, new_yhat_64b_popcounts, new_yhat_2kib_popcounts, new_yhat_64kib_popcounts, widx); uint32_t remainder = yhat_int % plink2::kBitsPerWord; uintptr_t cur_word = new_yhat_present[widx]; uintptr_t remainder_bit = plink2::k1LU << remainder; @@ -330,3 +346,296 @@ plink2::aligned_free(wkspace); return plink2::u63tod(numer_x2) / plink2::u63tod(denom * 2); } + +uint32_t WtSumLookup(const uint32_t* partial_wts, const uint32_t* partial_wts_64b, const uint32_t* partial_wts_1kib, const uint32_t* partial_wts_16kib, const uint32_t* partial_wts_256kib, const uint32_t* partial_wts_4mib, uint32_t yhat_int) { + uint32_t result = 0; + const uint32_t idx_4mib = yhat_int >> 20; + for (uint32_t uii = 0; uii != idx_4mib; ++uii) { + result += partial_wts_4mib[uii]; + } + const uint32_t idx_256kib = yhat_int >> 16; + for (uint32_t uii = idx_4mib << 4; uii != idx_256kib; ++uii) { + result += partial_wts_256kib[uii]; + } + const uint32_t idx_16kib = yhat_int >> 12; + for (uint32_t uii = idx_256kib << 4; uii != idx_16kib; ++uii) { + result += partial_wts_16kib[uii]; + } + const uint32_t idx_1kib = yhat_int >> 8; + for (uint32_t uii = idx_16kib << 4; uii != idx_1kib; ++uii) { + result += partial_wts_1kib[uii]; + } + const uint32_t idx_64b = yhat_int >> 4; + for (uint32_t uii = idx_1kib << 4; uii != idx_64b; ++uii) { + result += partial_wts_64b[uii]; + } + for (uint32_t uii = idx_64b << 4; uii != yhat_int; ++uii) { + result += partial_wts[uii]; + } + return result; +} + +typedef struct CIndexWeightedRecStruct { + double key; // yhat on first sort, y on second sort + uint32_t status; // original pos on first sort + uint32_t yhat_int; + uint32_t wt; + bool operator<(const struct CIndexWeightedRecStruct& rhs) const { + return (key < rhs.key); + } +} CIndexWeightedRec; + +#ifndef DBL_MAX +# define DBL_MAX 1.7976931348623157e308 +#endif + +double CIndexWeighted(NumericVector yhat, NumericVector y, SEXP status, const double* weightsd, uintptr_t orig_size) { + // Weighted C-index: + // sum_{j: status[j]=1} W[j] [sum_{m: y[m]>y[j]} [W[m] [1(yhat[m]y[j]} W[m]] + // + // To compute the numerator efficiently, we want to be able to look up + // partial-weight-sums according to yhat ranks. This can be accomplished + // with a "stack of indexes" similar to that used in the unweighted + // computation. + // Since the main bottlenecks now revolve around the weights, we quantize + // them to 32 bits by multiplying them by (constant / |max_wt|) and + // rounding to the nearest integer, where the constant is chosen to have good + // divisibility properties without risking denom_x2 64-bit integer overflow. + // (Note that this tends to be more, not less, accurate than using + // double-precision floating-point values for weight arithmetic. In the + // unlikely event that more weight accuracy is actually important, 128-bit + // integers should be preferred to extended-precision floating-point.) + uintptr_t size = 0; // We remove all 0-weight samples before the main loop. + double wt_multiplier; + double wt_min; + { + double wt_sum = 0.0; + double min_nz_wt = DBL_MAX; + for (uintptr_t ulii = 0; ulii != orig_size; ++ulii) { + const double cur_wt = weightsd[ulii]; + if (cur_wt < 0.0) { + stop("weights cannot be negative"); + } + if (cur_wt != 0.0) { + ++size; + wt_sum += cur_wt; + if (cur_wt < min_nz_wt) { + min_nz_wt = cur_wt; + } + } + } + if (wt_sum != wt_sum) { + stop("weights cannot be NaN"); + } + if (wt_sum > DBL_MAX) { + stop("weights are too large"); + } + if (!size) { + stop("at least one weight must be nonzero"); + } + if (size >= (1 << 24)) { + // Guarantee 8-bit or better precision for weights. + stop("too many nonzero-weight samples for current implementation"); + } + // denom_x2 is guaranteed to be less than sum(quantized weights)^2, so we + // just need to ensure sum(quantized weights) < 2^32. + // magic constant = 2^32 - 2^24. Since we have less than 2^24 samples with + // nonzero weights, this guarantees the sum of the quantized weights is + // less than 2^32 regardless of rounding errors. + if (min_nz_wt * 4278190080.0 >= wt_sum) { + // By choosing wt_multiplier to be a multiple of 1/min_nz_wt, we have + // better odds of representing all weights perfectly. + // 720720 is the smallest positive integer divisible by 1..16, 360360 is + // the smallest positive integer divisible by 1..15, etc. + if (min_nz_wt * (4278190080.0 / 720720) >= wt_sum) { + wt_multiplier = 720720.0 / min_nz_wt; + } else if (min_nz_wt * (4278190080.0 / 360360) >= wt_sum) { + wt_multiplier = 360360.0 / min_nz_wt; + } else if (min_nz_wt * (4278190080.0 / 27720) >= wt_sum) { + wt_multiplier = 27720.0 / min_nz_wt; + } else if (min_nz_wt * (4278190080.0 / 2520) >= wt_sum) { + wt_multiplier = 2520.0 / min_nz_wt; + } else if (min_nz_wt * (4278190080.0 / 840) >= wt_sum) { + wt_multiplier = 840.0 / min_nz_wt; + } else if (min_nz_wt * (4278190080.0 / 420) >= wt_sum) { + wt_multiplier = 420.0 / min_nz_wt; + } else if (min_nz_wt * (4278190080.0 / 60) >= wt_sum) { + wt_multiplier = 60.0 / min_nz_wt; + } else if (min_nz_wt * (4278190080.0 / 12) >= wt_sum) { + wt_multiplier = 12.0 / min_nz_wt; + } else if (min_nz_wt * (4278190080.0 / 6) >= wt_sum) { + wt_multiplier = 6.0 / min_nz_wt; + } else if (min_nz_wt * (4278190080.0 / 2) >= wt_sum) { + wt_multiplier = 2.0 / min_nz_wt; + } else { + wt_multiplier = 1.0 / min_nz_wt; + } + wt_min = min_nz_wt; + } else { + wt_multiplier = 4278190080.0 / wt_sum; + // In this case, it's possible for some more samples to be quantized to + // weight-zero. May as well throw those out too. + wt_min = 0.5 / wt_multiplier; + size = 0; + for (uintptr_t ulii = 0; ulii != orig_size; ++ulii) { + if (weightsd[ulii] >= wt_min) { + ++size; + } + } + } + if (wt_multiplier > DBL_MAX) { + stop("weights too small"); + } + } + const uintptr_t recs_vec_ct = plink2::DivUp(sizeof(CIndexWeightedRec) * size, plink2::kBytesPerVec); + // Tracking of partial-weight-sums worsens the constant factor by enough that + // there's little point in having a separate code path for the tie-light + // case. + const uintptr_t yhat_int_multiplicities_vec_ct = plink2::DivUp(size, plink2::kInt32PerVec); + + const uintptr_t partial_wts_cacheline_ct = plink2::DivUp(size, plink2::kInt32PerCacheline); + const uintptr_t partial_wts_64b_cacheline_ct = plink2::DivUp(partial_wts_cacheline_ct, plink2::kInt32PerCacheline); + const uintptr_t partial_wts_1kib_cacheline_ct = plink2::DivUp(partial_wts_64b_cacheline_ct, plink2::kInt32PerCacheline); + const uintptr_t partial_wts_16kib_cacheline_ct = plink2::DivUp(partial_wts_1kib_cacheline_ct, plink2::kInt32PerCacheline); + const uintptr_t partial_wts_256kib_cacheline_ct = plink2::DivUp(partial_wts_16kib_cacheline_ct, plink2::kInt32PerCacheline); + const uintptr_t partial_wts_4mib_cacheline_ct = plink2::DivUp(partial_wts_256kib_cacheline_ct, plink2::kInt32PerCacheline); + + const uintptr_t vec_ct = recs_vec_ct + 2 * yhat_int_multiplicities_vec_ct + (partial_wts_cacheline_ct + partial_wts_64b_cacheline_ct + partial_wts_1kib_cacheline_ct + partial_wts_16kib_cacheline_ct + partial_wts_256kib_cacheline_ct + partial_wts_4mib_cacheline_ct) * plink2::kVecsPerCacheline; + unsigned char* wkspace; + if (plink2::cachealigned_malloc(vec_ct * plink2::kBytesPerVec, &wkspace)) { + stop("Out of memory"); + } + memset(wkspace, 0, (vec_ct - recs_vec_ct) * plink2::kBytesPerVec); + unsigned char* wkspace_iter = wkspace; + // Put partial_wts arrays first to guarantee cacheline alignment, since it + // matters there. + uint32_t* partial_wts = reinterpret_cast(wkspace_iter); + wkspace_iter = &(wkspace_iter[partial_wts_cacheline_ct * plink2::kCacheline]); + uint32_t* partial_wts_64b = reinterpret_cast(wkspace_iter); + wkspace_iter = &(wkspace_iter[partial_wts_64b_cacheline_ct * plink2::kCacheline]); + uint32_t* partial_wts_1kib = reinterpret_cast(wkspace_iter); + wkspace_iter = &(wkspace_iter[partial_wts_1kib_cacheline_ct * plink2::kCacheline]); + uint32_t* partial_wts_16kib = reinterpret_cast(wkspace_iter); + wkspace_iter = &(wkspace_iter[partial_wts_16kib_cacheline_ct * plink2::kCacheline]); + uint32_t* partial_wts_256kib = reinterpret_cast(wkspace_iter); + wkspace_iter = &(wkspace_iter[partial_wts_256kib_cacheline_ct * plink2::kCacheline]); + uint32_t* partial_wts_4mib = reinterpret_cast(wkspace_iter); + wkspace_iter = &(wkspace_iter[partial_wts_4mib_cacheline_ct * plink2::kCacheline]); + + uint32_t* yhat_int_multiplicities = reinterpret_cast(wkspace_iter); + wkspace_iter = &(wkspace_iter[yhat_int_multiplicities_vec_ct * plink2::kBytesPerVec]); + uint32_t* tie_wt_sums = reinterpret_cast(wkspace_iter); + wkspace_iter = &(wkspace_iter[yhat_int_multiplicities_vec_ct * plink2::kBytesPerVec]); + + CIndexWeightedRec* recs = reinterpret_cast(wkspace_iter); + + const double* yd = &(y[0]); + { + const double* yhatd = &(yhat[0]); + uintptr_t new_idx = 0; + for (uintptr_t old_idx = 0; old_idx != orig_size; ++old_idx) { + const double cur_wt = weightsd[old_idx]; + if (cur_wt >= wt_min) { + recs[new_idx].key = yhatd[old_idx]; + recs[new_idx].wt = static_cast(wt_multiplier * cur_wt + 0.5); + recs[new_idx].status = old_idx; + ++new_idx; + } + } + std::sort(recs, &(recs[size])); + recs[0].yhat_int = 0; + double prev_yhat = recs[0].key; + recs[0].key = yd[recs[0].status]; + uintptr_t prev_idx = 0; + for (uintptr_t new_idx = 1; new_idx != size; ++new_idx) { + const double cur_yhat = recs[new_idx].key; + if (cur_yhat != prev_yhat) { + prev_idx = new_idx; + prev_yhat = cur_yhat; + } + recs[new_idx].yhat_int = prev_idx; + const uint32_t old_idx = recs[new_idx].status; + recs[new_idx].key = yd[old_idx]; + } + } + if (TYPEOF(status) == INTSXP) { + IntegerVector status_iv = as(status); + if (orig_size != static_cast(status_iv.size())) { + plink2::aligned_free(wkspace); + stop("y and status must have the same length"); + } + const int32_t* status_ivi = &(status_iv[0]); + for (uintptr_t new_idx = 0; new_idx != size; ++new_idx) { + const uint32_t old_idx = recs[new_idx].status; + const uint32_t cur_status = status_ivi[old_idx]; + if (cur_status & (~1)) { + plink2::aligned_free(wkspace); + stop("all status values must be 0 or 1"); + } + recs[new_idx].status = cur_status; + } + } else { + NumericVector status_nv = as(status); + if (orig_size != static_cast(status_nv.size())) { + plink2::aligned_free(wkspace); + stop("y and status must have the same length"); + } + const double* status_nvd = &(status_nv[0]); + for (uintptr_t new_idx = 0; new_idx != size; ++new_idx) { + const uint32_t old_idx = recs[new_idx].status; + const double dxx = status_nvd[old_idx]; + if (dxx == 0.0) { + recs[new_idx].status = 0; + } else if (dxx == 1.0) { + recs[new_idx].status = 1; + } else { + plink2::aligned_free(wkspace); + stop("all status values must be 0 or 1"); + } + } + } + std::sort(recs, &(recs[size])); + + double prev_key = recs[size - 1].key; + uint32_t recs_block_end = size; + uintptr_t higher_y_wt_sum = 0; + uint64_t numer_x2 = 0; + uint64_t denom = 0; + for (uint32_t recs_idx = size; recs_idx; ) { + --recs_idx; + const double cur_key = recs[recs_idx].key; + if (cur_key != prev_key) { + const uint32_t next_block_end = recs_idx + 1; + for (uint32_t uii = next_block_end; uii != recs_block_end; ++uii) { + const uint32_t yhat_int = recs[uii].yhat_int; + const uint32_t multiplicity = yhat_int_multiplicities[yhat_int]; + const uint32_t adj_yhat_int = yhat_int + multiplicity; + yhat_int_multiplicities[yhat_int] = multiplicity + 1; + + const uintptr_t cur_wt = recs[uii].wt; + tie_wt_sums[yhat_int] += cur_wt; + partial_wts[adj_yhat_int] = cur_wt; + partial_wts_64b[adj_yhat_int >> 4] += cur_wt; + partial_wts_1kib[adj_yhat_int >> 8] += cur_wt; + partial_wts_16kib[adj_yhat_int >> 12] += cur_wt; + partial_wts_256kib[adj_yhat_int >> 16] += cur_wt; + partial_wts_4mib[adj_yhat_int >> 20] += cur_wt; + higher_y_wt_sum += cur_wt; + } + recs_block_end = next_block_end; + prev_key = cur_key; + } + if (!recs[recs_idx].status) { + continue; + } + const uintptr_t cur_wt = recs[recs_idx].wt; + denom += cur_wt * static_cast(higher_y_wt_sum); + const uint32_t yhat_int = recs[recs_idx].yhat_int; + numer_x2 += cur_wt * (2 * static_cast(WtSumLookup(partial_wts, partial_wts_64b, partial_wts_1kib, partial_wts_16kib, partial_wts_256kib, partial_wts_4mib, yhat_int)) + tie_wt_sums[yhat_int]); + } + plink2::aligned_free(wkspace); + return plink2::u63tod(numer_x2) / plink2::u63tod(denom * 2); +} diff -Nru plink2-2.00~a3-200116+dfsg/cindex/src/RcppExports.cpp plink2-2.00~a3-200217+dfsg/cindex/src/RcppExports.cpp --- plink2-2.00~a3-200116+dfsg/cindex/src/RcppExports.cpp 2019-12-14 01:28:59.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/cindex/src/RcppExports.cpp 2020-01-19 16:30:40.000000000 +0000 @@ -6,21 +6,22 @@ using namespace Rcpp; // CIndex -double CIndex(NumericVector yhat, NumericVector y, SEXP status); -RcppExport SEXP _cindex_CIndex(SEXP yhatSEXP, SEXP ySEXP, SEXP statusSEXP) { +double CIndex(NumericVector yhat, NumericVector y, SEXP status, Nullable w); +RcppExport SEXP _cindex_CIndex(SEXP yhatSEXP, SEXP ySEXP, SEXP statusSEXP, SEXP wSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type yhat(yhatSEXP); Rcpp::traits::input_parameter< NumericVector >::type y(ySEXP); Rcpp::traits::input_parameter< SEXP >::type status(statusSEXP); - rcpp_result_gen = Rcpp::wrap(CIndex(yhat, y, status)); + Rcpp::traits::input_parameter< Nullable >::type w(wSEXP); + rcpp_result_gen = Rcpp::wrap(CIndex(yhat, y, status, w)); return rcpp_result_gen; END_RCPP } static const R_CallMethodDef CallEntries[] = { - {"_cindex_CIndex", (DL_FUNC) &_cindex_CIndex, 3}, + {"_cindex_CIndex", (DL_FUNC) &_cindex_CIndex, 4}, {NULL, NULL, 0} }; diff -Nru plink2-2.00~a3-200116+dfsg/debian/changelog plink2-2.00~a3-200217+dfsg/debian/changelog --- plink2-2.00~a3-200116+dfsg/debian/changelog 2020-01-29 06:54:52.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/debian/changelog 2020-02-19 05:58:25.000000000 +0000 @@ -1,3 +1,10 @@ +plink2 (2.00~a3-200217+dfsg-1) unstable; urgency=medium + + * New upstream release. + * Bump Standards-Version: 4.5.0 (no changes needed). + + -- Dylan Aïssi Wed, 19 Feb 2020 06:58:25 +0100 + plink2 (2.00~a3-200116+dfsg-2) unstable; urgency=medium * Restrict to any-amd64 and any-i386 architectures only. Others are diff -Nru plink2-2.00~a3-200116+dfsg/debian/control plink2-2.00~a3-200217+dfsg/debian/control --- plink2-2.00~a3-200116+dfsg/debian/control 2020-01-29 06:54:52.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/debian/control 2020-02-19 05:58:25.000000000 +0000 @@ -9,7 +9,7 @@ liblapack-dev, libzstd-dev (>= 1.4.4), zlib1g-dev -Standards-Version: 4.4.1 +Standards-Version: 4.5.0 Vcs-Browser: https://salsa.debian.org/med-team/plink2 Vcs-Git: https://salsa.debian.org/med-team/plink2.git Homepage: https://www.cog-genomics.org/plink/2.0/ diff -Nru plink2-2.00~a3-200116+dfsg/debian/upstream.docs/upstream.changelog plink2-2.00~a3-200217+dfsg/debian/upstream.docs/upstream.changelog --- plink2-2.00~a3-200116+dfsg/debian/upstream.docs/upstream.changelog 2020-01-29 06:54:52.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/debian/upstream.docs/upstream.changelog 2020-02-19 05:58:25.000000000 +0000 @@ -1,6 +1,20 @@ # Copy/Paste from https://www.cog-genomics.org/plink/2.0/ -16 Jan 2020: --pca allele/variant weight multithreading bugfix. +17 Feb 2020: --bcf implemented. + +11 Feb: "--vcf-half-call reference" works properly again (it was behaving like "--vcf-half-call error" in recent builds). + +8 Feb: BGZF-compressed text files should now work properly with all commands that make multiple passes over the file (previously they worked with --vcf, but almost no other commands of this type). Named-pipe input to these commands should now consistently result in an error message in a reasonable amount of time; previously this could hang forever. + +3 Feb: --missing-code now works properly with --haps. + +24 Jan: Fixed --extract/--exclude bug that could occur when another variant filter was applied earlier in the order of operations (e.g. --snps-only, --max-alleles, --extract-if-info). This bugfix has been backported to alpha 2. + +23 Jan: Added --bed-border-{bp,kb} flags for extending all "--extract range"/"--exclude range" intervals. + +21 Jan: "--extract range" and "--exclude range" no longer error out when their input files contain a chromosome code absent from the current dataset. + +16 Jan: --pca allele/variant weight multithreading bugfix. 14 Jan: --make-king-table rel-check bugfix. diff -Nru plink2-2.00~a3-200116+dfsg/include/plink2_base.h plink2-2.00~a3-200217+dfsg/include/plink2_base.h --- plink2-2.00~a3-200116+dfsg/include/plink2_base.h 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/include/plink2_base.h 2020-02-18 05:32:45.000000000 +0000 @@ -97,7 +97,7 @@ // 10000 * major + 100 * minor + patch // Exception to CONSTI32, since we want the preprocessor to have access // to this value. Named with all caps as a consequence. -#define PLINK2_BASE_VERNUM 700 +#define PLINK2_BASE_VERNUM 702 #define _FILE_OFFSET_BITS 64 @@ -347,6 +347,7 @@ kPglRetDecompressFail, // also distinguish this from MalformedInput kPglRetRewindFail, kPglRetSampleMajorBed = 32, + kPglRetNomemCustomMsg = 59, kPglRetInternalError = 60, kPglRetWarningErrcode = 61, kPglRetImproperFunctionCall = 62, @@ -414,6 +415,7 @@ const PglErr kPglRetRewindFail = PglErr::ec::kPglRetRewindFail; const PglErr kPglRetSampleMajorBed = PglErr::ec::kPglRetSampleMajorBed; const PglErr kPglRetWarningErrcode = PglErr::ec::kPglRetWarningErrcode; +const PglErr kPglRetNomemCustomMsg = PglErr::ec::kPglRetNomemCustomMsg; const PglErr kPglRetInternalError = PglErr::ec::kPglRetInternalError; const PglErr kPglRetImproperFunctionCall = PglErr::ec::kPglRetImproperFunctionCall; const PglErr kPglRetNotYetSupported = PglErr::ec::kPglRetNotYetSupported; @@ -708,7 +710,9 @@ typedef int32_t VecI32 __attribute__ ((vector_size (32))); typedef unsigned short VecU16 __attribute__ ((vector_size (32))); typedef short VecI16 __attribute__ ((vector_size (32))); -typedef char VecC __attribute__ ((vector_size (32))); +// documentation says 'char', but int8_t works fine under gcc 4.4 and conveys +// intent better (char not guaranteed to be signed) +typedef int8_t VecI8 __attribute__ ((vector_size (32))); typedef unsigned char VecUc __attribute__ ((vector_size (32))); # else CONSTI32(kBytesPerVec, 16); @@ -717,7 +721,7 @@ typedef int32_t VecI32 __attribute ((vector_size (16))); typedef unsigned short VecU16 __attribute__ ((vector_size (16))); typedef short VecI16 __attribute__ ((vector_size (16))); -typedef char VecC __attribute__ ((vector_size (16))); +typedef int8_t VecI8 __attribute__ ((vector_size (16))); typedef unsigned char VecUc __attribute__ ((vector_size (16))); # endif CONSTI32(kBitsPerWord, 64); @@ -755,8 +759,8 @@ return R_CAST(VecUc, _mm256_setzero_si256()); } -HEADER_INLINE VecC vecc_setzero() { - return R_CAST(VecC, _mm256_setzero_si256()); +HEADER_INLINE VecI8 veci8_setzero() { + return R_CAST(VecI8, _mm256_setzero_si256()); } // "vv >> ct" doesn't work, and Scientific Linux gcc 4.4 might not optimize @@ -769,6 +773,22 @@ return R_CAST(VecW, _mm256_slli_epi64(R_CAST(__m256i, vv), ct)); } +HEADER_INLINE VecU32 vecu32_srli(VecU32 vv, uint32_t ct) { + return R_CAST(VecU32, _mm256_srli_epi32(R_CAST(__m256i, vv), ct)); +} + +HEADER_INLINE VecU32 vecu32_slli(VecU32 vv, uint32_t ct) { + return R_CAST(VecU32, _mm256_slli_epi32(R_CAST(__m256i, vv), ct)); +} + +HEADER_INLINE VecU16 vecu16_srli(VecU16 vv, uint32_t ct) { + return R_CAST(VecU16, _mm256_srli_epi16(R_CAST(__m256i, vv), ct)); +} + +HEADER_INLINE VecU16 vecu16_slli(VecU16 vv, uint32_t ct) { + return R_CAST(VecU16, _mm256_slli_epi16(R_CAST(__m256i, vv), ct)); +} + // Compiler still doesn't seem to be smart enough to use andnot properly. HEADER_INLINE VecW vecw_and_notfirst(VecW excl, VecW main) { return R_CAST(VecW, _mm256_andnot_si256(R_CAST(__m256i, excl), R_CAST(__m256i, main))); @@ -778,6 +798,22 @@ return R_CAST(VecU32, _mm256_andnot_si256(R_CAST(__m256i, excl), R_CAST(__m256i, main))); } +HEADER_INLINE VecI32 veci32_and_notfirst(VecI32 excl, VecI32 main) { + return R_CAST(VecI32, _mm256_andnot_si256(R_CAST(__m256i, excl), R_CAST(__m256i, main))); +} + +HEADER_INLINE VecU16 vecu16_and_notfirst(VecU16 excl, VecU16 main) { + return R_CAST(VecU16, _mm256_andnot_si256(R_CAST(__m256i, excl), R_CAST(__m256i, main))); +} + +HEADER_INLINE VecI16 veci16_and_notfirst(VecI16 excl, VecI16 main) { + return R_CAST(VecI16, _mm256_andnot_si256(R_CAST(__m256i, excl), R_CAST(__m256i, main))); +} + +HEADER_INLINE VecI8 veci8_and_notfirst(VecI8 excl, VecI8 main) { + return R_CAST(VecI8, _mm256_andnot_si256(R_CAST(__m256i, excl), R_CAST(__m256i, main))); +} + HEADER_INLINE VecW vecw_set1(uintptr_t ulii) { return R_CAST(VecW, _mm256_set1_epi64x(ulii)); } @@ -802,8 +838,8 @@ return R_CAST(VecUc, _mm256_set1_epi8(ucc)); } -HEADER_INLINE VecC vecc_set1(char cc) { - return R_CAST(VecC, _mm256_set1_epi8(cc)); +HEADER_INLINE VecI8 veci8_set1(char cc) { + return R_CAST(VecI8, _mm256_set1_epi8(cc)); } HEADER_INLINE uint32_t vecw_movemask(VecW vv) { @@ -814,11 +850,19 @@ return _mm256_movemask_epi8(R_CAST(__m256i, vv)); } +HEADER_INLINE uint32_t veci32_movemask(VecI32 vv) { + return _mm256_movemask_epi8(R_CAST(__m256i, vv)); +} + HEADER_INLINE uint32_t vecu16_movemask(VecU16 vv) { return _mm256_movemask_epi8(R_CAST(__m256i, vv)); } -HEADER_INLINE uint32_t vecc_movemask(VecC vv) { +HEADER_INLINE uint32_t veci16_movemask(VecI16 vv) { + return _mm256_movemask_epi8(R_CAST(__m256i, vv)); +} + +HEADER_INLINE uint32_t veci8_movemask(VecI8 vv) { return _mm256_movemask_epi8(R_CAST(__m256i, vv)); } @@ -831,6 +875,10 @@ return R_CAST(VecW, _mm256_setr_epi8(e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0, e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0)); } +HEADER_INLINE VecU16 vecu16_setr8(char e15, char e14, char e13, char e12, char e11, char e10, char e9, char e8, char e7, char e6, char e5, char e4, char e3, char e2, char e1, char e0) { + return R_CAST(VecU16, _mm256_setr_epi8(e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0, e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0)); +} + HEADER_INLINE VecUc vecuc_setr8(char e15, char e14, char e13, char e12, char e11, char e10, char e9, char e8, char e7, char e6, char e5, char e4, char e3, char e2, char e1, char e0) { return R_CAST(VecUc, _mm256_setr_epi8(e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0, e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0)); } @@ -848,6 +896,22 @@ return R_CAST(VecW, _mm256_unpackhi_epi8(R_CAST(__m256i, evens), R_CAST(__m256i, odds))); } +HEADER_INLINE VecI8 veci8_unpacklo8(VecI8 evens, VecI8 odds) { + return R_CAST(VecI8, _mm256_unpacklo_epi8(R_CAST(__m256i, evens), R_CAST(__m256i, odds))); +} + +HEADER_INLINE VecI8 veci8_unpackhi8(VecI8 evens, VecI8 odds) { + return R_CAST(VecI8, _mm256_unpackhi_epi8(R_CAST(__m256i, evens), R_CAST(__m256i, odds))); +} + +HEADER_INLINE VecUc vecuc_unpacklo8(VecUc evens, VecUc odds) { + return R_CAST(VecUc, _mm256_unpacklo_epi8(R_CAST(__m256i, evens), R_CAST(__m256i, odds))); +} + +HEADER_INLINE VecUc vecuc_unpackhi8(VecUc evens, VecUc odds) { + return R_CAST(VecUc, _mm256_unpackhi_epi8(R_CAST(__m256i, evens), R_CAST(__m256i, odds))); +} + HEADER_INLINE VecW vecw_unpacklo16(VecW evens, VecW odds) { return R_CAST(VecW, _mm256_unpacklo_epi16(R_CAST(__m256i, evens), R_CAST(__m256i, odds))); } @@ -868,6 +932,14 @@ return R_CAST(VecW, _mm256_permute4x64_epi64(R_CAST(__m256i, vv), 0xd8)); } +HEADER_INLINE VecI8 veci8_permute0xd8_if_avx2(VecI8 vv) { + return R_CAST(VecI8, _mm256_permute4x64_epi64(R_CAST(__m256i, vv), 0xd8)); +} + +HEADER_INLINE VecUc vecuc_permute0xd8_if_avx2(VecUc vv) { + return R_CAST(VecUc, _mm256_permute4x64_epi64(R_CAST(__m256i, vv), 0xd8)); +} + HEADER_INLINE VecW vecw_shuffle8(VecW table, VecW indexes) { return R_CAST(VecW, _mm256_shuffle_epi8(R_CAST(__m256i, table), R_CAST(__m256i, indexes))); } @@ -939,6 +1011,10 @@ return R_CAST(VecUc, _mm256_loadu_si256(S_CAST(const __m256i*, mem_addr))); } +HEADER_INLINE VecI8 veci8_loadu(const void* mem_addr) { + return R_CAST(VecI8, _mm256_loadu_si256(S_CAST(const __m256i*, mem_addr))); +} + HEADER_INLINE void vecw_storeu(void* mem_addr, VecW vv) { _mm256_storeu_si256(S_CAST(__m256i*, mem_addr), R_CAST(__m256i, vv)); } @@ -975,6 +1051,14 @@ return R_CAST(VecUc, _mm256_adds_epu8(R_CAST(__m256i, v1), R_CAST(__m256i, v2))); } +HEADER_INLINE VecU16 vecu16_min8(VecU16 v1, VecU16 v2) { + return R_CAST(VecU16, _mm256_min_epu8(R_CAST(__m256i, v1), R_CAST(__m256i, v2))); +} + +HEADER_INLINE VecUc vecuc_min(VecUc v1, VecUc v2) { + return R_CAST(VecUc, _mm256_min_epu8(R_CAST(__m256i, v1), R_CAST(__m256i, v2))); +} + # else // !USE_AVX2 # define VCONST_W(xx) {xx, xx} @@ -1002,8 +1086,8 @@ return R_CAST(VecUc, _mm_setzero_si128()); } -HEADER_INLINE VecC vecc_setzero() { - return R_CAST(VecC, _mm_setzero_si128()); +HEADER_INLINE VecI8 veci8_setzero() { + return R_CAST(VecI8, _mm_setzero_si128()); } HEADER_INLINE VecW vecw_srli(VecW vv, uint32_t ct) { @@ -1014,6 +1098,22 @@ return R_CAST(VecW, _mm_slli_epi64(R_CAST(__m128i, vv), ct)); } +HEADER_INLINE VecU32 vecu32_srli(VecU32 vv, uint32_t ct) { + return R_CAST(VecU32, _mm_srli_epi32(R_CAST(__m128i, vv), ct)); +} + +HEADER_INLINE VecU32 vecu32_slli(VecU32 vv, uint32_t ct) { + return R_CAST(VecU32, _mm_slli_epi32(R_CAST(__m128i, vv), ct)); +} + +HEADER_INLINE VecU16 vecu16_srli(VecU16 vv, uint32_t ct) { + return R_CAST(VecU16, _mm_srli_epi16(R_CAST(__m128i, vv), ct)); +} + +HEADER_INLINE VecU16 vecu16_slli(VecU16 vv, uint32_t ct) { + return R_CAST(VecU16, _mm_slli_epi16(R_CAST(__m128i, vv), ct)); +} + HEADER_INLINE VecW vecw_and_notfirst(VecW excl, VecW main) { return R_CAST(VecW, _mm_andnot_si128(R_CAST(__m128i, excl), R_CAST(__m128i, main))); } @@ -1022,6 +1122,22 @@ return R_CAST(VecU32, _mm_andnot_si128(R_CAST(__m128i, excl), R_CAST(__m128i, main))); } +HEADER_INLINE VecI32 veci32_and_notfirst(VecI32 excl, VecI32 main) { + return R_CAST(VecI32, _mm_andnot_si128(R_CAST(__m128i, excl), R_CAST(__m128i, main))); +} + +HEADER_INLINE VecU16 vecu16_and_notfirst(VecU16 excl, VecU16 main) { + return R_CAST(VecU16, _mm_andnot_si128(R_CAST(__m128i, excl), R_CAST(__m128i, main))); +} + +HEADER_INLINE VecI16 veci16_and_notfirst(VecI16 excl, VecI16 main) { + return R_CAST(VecI16, _mm_andnot_si128(R_CAST(__m128i, excl), R_CAST(__m128i, main))); +} + +HEADER_INLINE VecI8 veci8_and_notfirst(VecI8 excl, VecI8 main) { + return R_CAST(VecI8, _mm_andnot_si128(R_CAST(__m128i, excl), R_CAST(__m128i, main))); +} + HEADER_INLINE VecW vecw_set1(uintptr_t ulii) { return R_CAST(VecW, _mm_set1_epi64x(ulii)); } @@ -1038,16 +1154,16 @@ return R_CAST(VecU16, _mm_set1_epi16(usi)); } -HEADER_INLINE VecI32 veci16_set1(short si) { - return R_CAST(VecI32, _mm_set1_epi16(si)); +HEADER_INLINE VecI16 veci16_set1(short si) { + return R_CAST(VecI16, _mm_set1_epi16(si)); } HEADER_INLINE VecUc vecuc_set1(unsigned char ucc) { return R_CAST(VecUc, _mm_set1_epi8(ucc)); } -HEADER_INLINE VecC vecc_set1(char cc) { - return R_CAST(VecC, _mm_set1_epi8(cc)); +HEADER_INLINE VecI8 veci8_set1(char cc) { + return R_CAST(VecI8, _mm_set1_epi8(cc)); } HEADER_INLINE uint32_t vecw_movemask(VecW vv) { @@ -1058,11 +1174,19 @@ return _mm_movemask_epi8(R_CAST(__m128i, vv)); } +HEADER_INLINE uint32_t veci32_movemask(VecI32 vv) { + return _mm_movemask_epi8(R_CAST(__m128i, vv)); +} + HEADER_INLINE uint32_t vecu16_movemask(VecU16 vv) { return _mm_movemask_epi8(R_CAST(__m128i, vv)); } -HEADER_INLINE uint32_t vecc_movemask(VecC vv) { +HEADER_INLINE uint32_t veci16_movemask(VecI16 vv) { + return _mm_movemask_epi8(R_CAST(__m128i, vv)); +} + +HEADER_INLINE uint32_t veci8_movemask(VecI8 vv) { return _mm_movemask_epi8(R_CAST(__m128i, vv)); } @@ -1106,6 +1230,10 @@ return R_CAST(VecUc, _mm_loadu_si128(S_CAST(const __m128i*, mem_addr))); } +HEADER_INLINE VecI8 veci8_loadu(const void* mem_addr) { + return R_CAST(VecI8, _mm_loadu_si128(S_CAST(const __m128i*, mem_addr))); +} + HEADER_INLINE void vecw_storeu(void* mem_addr, VecW vv) { _mm_storeu_si128(S_CAST(__m128i*, mem_addr), R_CAST(__m128i, vv)); } @@ -1131,6 +1259,10 @@ return R_CAST(VecW, _mm_setr_epi8(e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0)); } +HEADER_INLINE VecU16 vecu16_setr8(char e15, char e14, char e13, char e12, char e11, char e10, char e9, char e8, char e7, char e6, char e5, char e4, char e3, char e2, char e1, char e0) { + return R_CAST(VecU16, _mm_setr_epi8(e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0)); +} + HEADER_INLINE VecUc vecuc_setr8(char e15, char e14, char e13, char e12, char e11, char e10, char e9, char e8, char e7, char e6, char e5, char e4, char e3, char e2, char e1, char e0) { return R_CAST(VecUc, _mm_setr_epi8(e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0)); } @@ -1160,6 +1292,22 @@ return R_CAST(VecW, _mm_unpackhi_epi8(R_CAST(__m128i, evens), R_CAST(__m128i, odds))); } +HEADER_INLINE VecI8 veci8_unpacklo8(VecI8 evens, VecI8 odds) { + return R_CAST(VecI8, _mm_unpacklo_epi8(R_CAST(__m128i, evens), R_CAST(__m128i, odds))); +} + +HEADER_INLINE VecI8 veci8_unpackhi8(VecI8 evens, VecI8 odds) { + return R_CAST(VecI8, _mm_unpackhi_epi8(R_CAST(__m128i, evens), R_CAST(__m128i, odds))); +} + +HEADER_INLINE VecUc vecuc_unpacklo8(VecUc evens, VecUc odds) { + return R_CAST(VecUc, _mm_unpacklo_epi8(R_CAST(__m128i, evens), R_CAST(__m128i, odds))); +} + +HEADER_INLINE VecUc vecuc_unpackhi8(VecUc evens, VecUc odds) { + return R_CAST(VecUc, _mm_unpackhi_epi8(R_CAST(__m128i, evens), R_CAST(__m128i, odds))); +} + HEADER_INLINE VecW vecw_unpacklo16(VecW evens, VecW odds) { return R_CAST(VecW, _mm_unpacklo_epi16(R_CAST(__m128i, evens), R_CAST(__m128i, odds))); } @@ -1188,6 +1336,14 @@ return vv; } +HEADER_INLINE VecI8 veci8_permute0xd8_if_avx2(VecI8 vv) { + return vv; +} + +HEADER_INLINE VecUc vecuc_permute0xd8_if_avx2(VecUc vv) { + return vv; +} + # ifdef USE_SSE42 HEADER_INLINE VecI32 veci32_max(VecI32 v1, VecI32 v2) { return R_CAST(VecI32, _mm_max_epi32(R_CAST(__m128i, v1), R_CAST(__m128i, v2))); @@ -1235,6 +1391,14 @@ return R_CAST(VecUc, _mm_adds_epu8(R_CAST(__m128i, v1), R_CAST(__m128i, v2))); } +HEADER_INLINE VecU16 vecu16_min8(VecU16 v1, VecU16 v2) { + return R_CAST(VecU16, _mm_min_epu8(R_CAST(__m128i, v1), R_CAST(__m128i, v2))); +} + +HEADER_INLINE VecUc vecuc_min(VecUc v1, VecUc v2) { + return R_CAST(VecUc, _mm_min_epu8(R_CAST(__m128i, v1), R_CAST(__m128i, v2))); +} + # endif // !USE_AVX2 HEADER_INLINE VecW vecw_bytesum(VecW src, VecW m0) { @@ -1283,7 +1447,7 @@ typedef uintptr_t VecW; typedef uintptr_t VecU32; typedef float VecF; -// VecI16 and VecC aren't worth the trouble of scaling down to 32-bit +// VecI16 and VecI8 aren't worth the trouble of scaling down to 32-bit # define VCONST_W(xx) (xx) @@ -1321,6 +1485,15 @@ } #endif // !__LP64__ +// debug +HEADER_INLINE void PrintVec(const void* vv) { + const unsigned char* vv_alias = S_CAST(const unsigned char*, vv); + for (uint32_t uii = 0; uii != kBytesPerVec; ++uii) { + printf("%u ", vv_alias[uii]); + } + printf("\n"); +} + // Unfortunately, we need to spell out S_CAST(uintptr_t, 0) instead of just // typing k0LU in C99. static const uintptr_t kMask5555 = (~S_CAST(uintptr_t, 0)) / 3; diff -Nru plink2-2.00~a3-200116+dfsg/include/plink2_bgzf.cc plink2-2.00~a3-200217+dfsg/include/plink2_bgzf.cc --- plink2-2.00~a3-200116+dfsg/include/plink2_bgzf.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/include/plink2_bgzf.cc 2020-02-09 00:25:05.000000000 +0000 @@ -510,6 +510,10 @@ next_cwr->locked_start = kBgzfRawMtStreamRetargetCode; if (next_ff == nullptr) { rewind(bodyp->ff); + // bugfix (8 Feb 2020): need to explicitly read the first 16 bytes. + if (unlikely(!fread_unlocked(bodyp->in, 16, 1, bodyp->ff))) { + return kPglRetRewindFail; + } } else { // Caller is responsible for closing previous bodyp->ff, etc. bodyp->ff = next_ff; diff -Nru plink2-2.00~a3-200116+dfsg/include/plink2_bgzf.h plink2-2.00~a3-200217+dfsg/include/plink2_bgzf.h --- plink2-2.00~a3-200116+dfsg/include/plink2_bgzf.h 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/include/plink2_bgzf.h 2020-01-31 03:55:43.000000000 +0000 @@ -144,6 +144,8 @@ extern const char kShortErrInvalidBgzf[]; +void PreinitBgzfRawMtStream(BgzfRawMtDecompressStream* bgzfp); + // Two modes: // - Regular: ff must point 16 bytes into the file, and header[] must contain // the first 16 bytes. bgzf_st_ptr must be nullptr. diff -Nru plink2-2.00~a3-200116+dfsg/include/plink2_string.cc plink2-2.00~a3-200217+dfsg/include/plink2_string.cc --- plink2-2.00~a3-200116+dfsg/include/plink2_string.cc 2020-01-14 20:23:25.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/include/plink2_string.cc 2020-02-07 05:13:17.000000000 +0000 @@ -24,11 +24,11 @@ #if defined(__LP64__) && !defined(_GNU_SOURCE) CXXCONST_VOIDP rawmemchr(const void* ss, int cc) { const uintptr_t starting_addr = R_CAST(uintptr_t, ss); - const VecC* ss_viter = R_CAST(const VecC*, RoundDownPow2(starting_addr, kBytesPerVec)); - const VecC vvec_all_needle = vecc_set1(cc); - VecC cur_vvec = *ss_viter; - VecC needle_match_vvec = (cur_vvec == vvec_all_needle); - uint32_t matching_bytes = vecc_movemask(needle_match_vvec); + const VecI8* ss_viter = R_CAST(const VecI8*, RoundDownPow2(starting_addr, kBytesPerVec)); + const VecI8 vvec_all_needle = veci8_set1(cc); + VecI8 cur_vvec = *ss_viter; + VecI8 needle_match_vvec = (cur_vvec == vvec_all_needle); + uint32_t matching_bytes = veci8_movemask(needle_match_vvec); const uint32_t leading_byte_ct = starting_addr - R_CAST(uintptr_t, ss_viter); matching_bytes &= UINT32_MAX << leading_byte_ct; // This is typically short-range, so the Memrchr() double-vector strategy is @@ -37,7 +37,7 @@ ++ss_viter; cur_vvec = *ss_viter; needle_match_vvec = (cur_vvec == vvec_all_needle); - matching_bytes = vecc_movemask(needle_match_vvec); + matching_bytes = veci8_movemask(needle_match_vvec); } const uint32_t byte_offset_in_vec = ctzu32(matching_bytes); return R_CAST(CXXCONST_VOIDP, R_CAST(uintptr_t, ss_viter) + byte_offset_in_vec); @@ -47,13 +47,13 @@ #ifdef __LP64__ CXXCONST_VOIDP rawmemchr2(const void* ss, unsigned char ucc1, unsigned char ucc2) { const uintptr_t starting_addr = R_CAST(uintptr_t, ss); - const VecC* ss_viter = R_CAST(const VecC*, RoundDownPow2(starting_addr, kBytesPerVec)); - const VecC vvec_all_ucc1 = vecc_set1(ucc1); - const VecC vvec_all_ucc2 = vecc_set1(ucc2); - VecC cur_vvec = *ss_viter; - VecC ucc1_match_vvec = (cur_vvec == vvec_all_ucc1); - VecC ucc2_match_vvec = (cur_vvec == vvec_all_ucc2); - uint32_t matching_bytes = vecc_movemask(ucc1_match_vvec | ucc2_match_vvec); + const VecI8* ss_viter = R_CAST(const VecI8*, RoundDownPow2(starting_addr, kBytesPerVec)); + const VecI8 vvec_all_ucc1 = veci8_set1(ucc1); + const VecI8 vvec_all_ucc2 = veci8_set1(ucc2); + VecI8 cur_vvec = *ss_viter; + VecI8 ucc1_match_vvec = (cur_vvec == vvec_all_ucc1); + VecI8 ucc2_match_vvec = (cur_vvec == vvec_all_ucc2); + uint32_t matching_bytes = veci8_movemask(ucc1_match_vvec | ucc2_match_vvec); const uint32_t leading_byte_ct = starting_addr - R_CAST(uintptr_t, ss_viter); matching_bytes &= UINT32_MAX << leading_byte_ct; while (!matching_bytes) { @@ -61,32 +61,32 @@ cur_vvec = *ss_viter; ucc1_match_vvec = (cur_vvec == vvec_all_ucc1); ucc2_match_vvec = (cur_vvec == vvec_all_ucc2); - matching_bytes = vecc_movemask(ucc1_match_vvec | ucc2_match_vvec); + matching_bytes = veci8_movemask(ucc1_match_vvec | ucc2_match_vvec); } const uint32_t byte_offset_in_vec = ctzu32(matching_bytes); return &(R_CAST(CXXCONST_CP, ss_viter)[byte_offset_in_vec]); } -CXXCONST_CP strchrnul2(const char* ss, unsigned char ucc1, unsigned char ucc2) { +CXXCONST_VOIDP rawmemchr3(const void* ss, unsigned char ucc1, unsigned char ucc2, unsigned char ucc3) { const uintptr_t starting_addr = R_CAST(uintptr_t, ss); - const VecC* ss_viter = R_CAST(const VecC*, RoundDownPow2(starting_addr, kBytesPerVec)); - const VecC vvec_all_zero = vecc_setzero(); - const VecC vvec_all_ucc1 = vecc_set1(ucc1); - const VecC vvec_all_ucc2 = vecc_set1(ucc2); - VecC cur_vvec = *ss_viter; - VecC zero_match_vvec = (cur_vvec == vvec_all_zero); - VecC ucc1_match_vvec = (cur_vvec == vvec_all_ucc1); - VecC ucc2_match_vvec = (cur_vvec == vvec_all_ucc2); - uint32_t matching_bytes = vecc_movemask(zero_match_vvec | ucc1_match_vvec | ucc2_match_vvec); + const VecI8* ss_viter = R_CAST(const VecI8*, RoundDownPow2(starting_addr, kBytesPerVec)); + const VecI8 vvec_all_ucc1 = veci8_set1(ucc1); + const VecI8 vvec_all_ucc2 = veci8_set1(ucc2); + const VecI8 vvec_all_ucc3 = veci8_set1(ucc3); + VecI8 cur_vvec = *ss_viter; + VecI8 ucc1_match_vvec = (cur_vvec == vvec_all_ucc1); + VecI8 ucc2_match_vvec = (cur_vvec == vvec_all_ucc2); + VecI8 ucc3_match_vvec = (cur_vvec == vvec_all_ucc3); + uint32_t matching_bytes = veci8_movemask(ucc1_match_vvec | ucc2_match_vvec | ucc3_match_vvec); const uint32_t leading_byte_ct = starting_addr - R_CAST(uintptr_t, ss_viter); matching_bytes &= UINT32_MAX << leading_byte_ct; while (!matching_bytes) { ++ss_viter; cur_vvec = *ss_viter; - zero_match_vvec = (cur_vvec == vvec_all_zero); ucc1_match_vvec = (cur_vvec == vvec_all_ucc1); ucc2_match_vvec = (cur_vvec == vvec_all_ucc2); - matching_bytes = vecc_movemask(zero_match_vvec | ucc1_match_vvec | ucc2_match_vvec); + ucc3_match_vvec = (cur_vvec == vvec_all_ucc3); + matching_bytes = veci8_movemask(ucc1_match_vvec | ucc2_match_vvec | ucc3_match_vvec); } const uint32_t byte_offset_in_vec = ctzu32(matching_bytes); return &(R_CAST(CXXCONST_CP, ss_viter)[byte_offset_in_vec]); @@ -94,17 +94,17 @@ CXXCONST_CP strchrnul3(const char* ss, unsigned char ucc1, unsigned char ucc2, unsigned char ucc3) { const uintptr_t starting_addr = R_CAST(uintptr_t, ss); - const VecC* ss_viter = R_CAST(const VecC*, RoundDownPow2(starting_addr, kBytesPerVec)); - const VecC vvec_all_zero = vecc_setzero(); - const VecC vvec_all_ucc1 = vecc_set1(ucc1); - const VecC vvec_all_ucc2 = vecc_set1(ucc2); - const VecC vvec_all_ucc3 = vecc_set1(ucc3); - VecC cur_vvec = *ss_viter; - VecC zero_match_vvec = (cur_vvec == vvec_all_zero); - VecC ucc1_match_vvec = (cur_vvec == vvec_all_ucc1); - VecC ucc2_match_vvec = (cur_vvec == vvec_all_ucc2); - VecC ucc3_match_vvec = (cur_vvec == vvec_all_ucc3); - uint32_t matching_bytes = vecc_movemask(zero_match_vvec | ucc1_match_vvec | ucc2_match_vvec | ucc3_match_vvec); + const VecI8* ss_viter = R_CAST(const VecI8*, RoundDownPow2(starting_addr, kBytesPerVec)); + const VecI8 vvec_all_zero = veci8_setzero(); + const VecI8 vvec_all_ucc1 = veci8_set1(ucc1); + const VecI8 vvec_all_ucc2 = veci8_set1(ucc2); + const VecI8 vvec_all_ucc3 = veci8_set1(ucc3); + VecI8 cur_vvec = *ss_viter; + VecI8 zero_match_vvec = (cur_vvec == vvec_all_zero); + VecI8 ucc1_match_vvec = (cur_vvec == vvec_all_ucc1); + VecI8 ucc2_match_vvec = (cur_vvec == vvec_all_ucc2); + VecI8 ucc3_match_vvec = (cur_vvec == vvec_all_ucc3); + uint32_t matching_bytes = veci8_movemask(zero_match_vvec | ucc1_match_vvec | ucc2_match_vvec | ucc3_match_vvec); const uint32_t leading_byte_ct = starting_addr - R_CAST(uintptr_t, ss_viter); matching_bytes &= UINT32_MAX << leading_byte_ct; while (!matching_bytes) { @@ -114,7 +114,7 @@ ucc1_match_vvec = (cur_vvec == vvec_all_ucc1); ucc2_match_vvec = (cur_vvec == vvec_all_ucc2); ucc3_match_vvec = (cur_vvec == vvec_all_ucc3); - matching_bytes = vecc_movemask(zero_match_vvec | ucc1_match_vvec | ucc2_match_vvec | ucc3_match_vvec); + matching_bytes = veci8_movemask(zero_match_vvec | ucc1_match_vvec | ucc2_match_vvec | ucc3_match_vvec); } const uint32_t byte_offset_in_vec = ctzu32(matching_bytes); return &(R_CAST(CXXCONST_CP, ss_viter)[byte_offset_in_vec]); @@ -129,11 +129,11 @@ } } -CXXCONST_CP strchrnul2(const char* ss, unsigned char ucc1, unsigned char ucc2) { - for (; ; ++ss) { - unsigned char ucc = *ss; - if ((!ucc) || (ucc == ucc1) || (ucc == ucc2)) { - return S_CAST(CXXCONST_CP, ss); +CXXCONST_VOIDP rawmemchr3(const void* ss, unsigned char ucc1, unsigned char ucc2, unsigned char ucc3) { + for (const unsigned char* ss_iter = S_CAST(const unsigned char*, ss); ; ++ss_iter) { + unsigned char ucc = *ss_iter; + if ((ucc == ucc1) || (ucc == ucc2) || (ucc == ucc3)) { + return S_CAST(CXXCONST_VOIDP, ss_iter); } } } @@ -3094,10 +3094,10 @@ #ifdef __LP64__ CXXCONST_CP Memrchr(const char* str_start, char needle, uintptr_t slen) { - const VecC vvec_all_needle = vecc_set1(needle); + const VecI8 vvec_all_needle = veci8_set1(needle); const uintptr_t str_end_addr = R_CAST(uintptr_t, str_start) + slen; const uint32_t trailing_byte_ct = str_end_addr % kBytesPerVec; - const VecC* str_rev_viter = R_CAST(const VecC*, RoundDownPow2(str_end_addr, kBytesPerVec)); + const VecI8* str_rev_viter = R_CAST(const VecI8*, RoundDownPow2(str_end_addr, kBytesPerVec)); if (trailing_byte_ct) { // This is a GNU vector extension parallel-equality check, which gets // translated to e.g. _mm256_cmpeq_epi8(). @@ -3105,8 +3105,8 @@ // beyond str_end as long as they're in the same vector; we only risk // violating process read permissions if we cross a page boundary. // (For this reason, I don't bother with AVX unaligned reads.) - const VecC match_vvec = (*str_rev_viter == vvec_all_needle); - uint32_t matching_bytes = vecc_movemask(match_vvec); + const VecI8 match_vvec = (*str_rev_viter == vvec_all_needle); + uint32_t matching_bytes = veci8_movemask(match_vvec); matching_bytes &= (1U << (trailing_byte_ct % kBytesPerVec)) - 1; if (str_start > R_CAST(const char*, str_rev_viter)) { const uint32_t leading_byte_ct = R_CAST(uintptr_t, str_start) % kBytesPerVec; @@ -3126,12 +3126,12 @@ // For long lines, looping over two vectors at a time is most efficient on // my Mac (also tried 1 and 4). --str_rev_viter; - const VecC match_vvec1 = (*str_rev_viter == vvec_all_needle); + const VecI8 match_vvec1 = (*str_rev_viter == vvec_all_needle); --str_rev_viter; - const VecC match_vvec0 = (*str_rev_viter == vvec_all_needle); - const uint32_t matching_bytes = vecc_movemask(match_vvec1 | match_vvec0); + const VecI8 match_vvec0 = (*str_rev_viter == vvec_all_needle); + const uint32_t matching_bytes = veci8_movemask(match_vvec1 | match_vvec0); if (matching_bytes) { - const uint32_t matching_bytes1 = vecc_movemask(match_vvec1); + const uint32_t matching_bytes1 = veci8_movemask(match_vvec1); if (matching_bytes1) { const uint32_t byte_offset_in_vec = bsru32(matching_bytes1); return &(R_CAST(CXXCONST_CP, &(str_rev_viter[1]))[byte_offset_in_vec]); @@ -3146,8 +3146,8 @@ return nullptr; } --str_rev_viter; - const VecC match_vvec = (*str_rev_viter == vvec_all_needle); - const uint32_t matching_bytes = vecc_movemask(match_vvec); + const VecI8 match_vvec = (*str_rev_viter == vvec_all_needle); + const uint32_t matching_bytes = veci8_movemask(match_vvec); if (matching_bytes) { const uint32_t byte_offset_in_vec = bsru32(matching_bytes); if (byte_offset_in_vec + remaining_byte_ct_underflow < kBytesPerVec) { diff -Nru plink2-2.00~a3-200116+dfsg/include/plink2_string.h plink2-2.00~a3-200217+dfsg/include/plink2_string.h --- plink2-2.00~a3-200116+dfsg/include/plink2_string.h 2020-01-14 20:23:25.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/include/plink2_string.h 2020-02-12 01:15:02.000000000 +0000 @@ -209,7 +209,15 @@ return S_CAST(CXXCONST_CP, rawmemchr2(ss, ucc1, '\n')); } -CXXCONST_CP strchrnul2(const char* ss, unsigned char ucc1, unsigned char ucc2); +CXXCONST_VOIDP rawmemchr3(const void* ss, unsigned char ucc1, unsigned char ucc2, unsigned char ucc3); + +HEADER_INLINE CXXCONST_CP strchrnul2(const char* ss, unsigned char ucc1, unsigned char ucc2) { + return S_CAST(CXXCONST_CP, rawmemchr3(ss, ucc1, ucc2, '\0')); +} + +HEADER_INLINE CXXCONST_CP strchrnul2_n(const char* ss, unsigned char ucc1, unsigned char ucc2) { + return S_CAST(CXXCONST_CP, rawmemchr3(ss, ucc1, ucc2, '\n')); +} CXXCONST_CP strchrnul3(const char* ss, unsigned char ucc1, unsigned char ucc2, unsigned char ucc3); @@ -222,10 +230,18 @@ return const_cast(strchrnul_n(const_cast(ss), ucc1)); } +HEADER_INLINE void* rawmemchr3(void* ss, unsigned char ucc1, unsigned char ucc2, unsigned char ucc3) { + return const_cast(rawmemchr3(const_cast(ss), ucc1, ucc2, ucc3)); +} + HEADER_INLINE char* strchrnul2(char* ss, unsigned char ucc1, unsigned char ucc2) { return const_cast(strchrnul2(const_cast(ss), ucc1, ucc2)); } +HEADER_INLINE char* strchrnul2_n(char* ss, unsigned char ucc1, unsigned char ucc2) { + return const_cast(strchrnul2_n(const_cast(ss), ucc1, ucc2)); +} + HEADER_INLINE char* strchrnul3(char* ss, unsigned char ucc1, unsigned char ucc2, unsigned char ucc3) { return const_cast(strchrnul3(const_cast(ss), ucc1, ucc2, ucc3)); } @@ -752,8 +768,8 @@ // trusted to use movemask: // strlen, strchr, memchr // rawmemchr, strchrnul -// rawmemchr2, strnul, strchrnul_n, strchrnul2, strchrnul3, strchrnul_n_mov, -// incr_strchrnul_n_mov +// rawmemchr2, rawmemchr3, strnul, strchrnul_n, strchrnul2, strchrnul3, +// strchrnul_n_mov, incr_strchrnul_n_mov // NextTokenMultFar // AdvToNthDelimChecked, AdvToNthDelim, AdvToDelimOrEnd, Memrchr, // LastSpaceOrEoln @@ -1365,6 +1381,18 @@ char* u32toa_trunc4(uint32_t uii, char* start); +// Write-buffer-sizing constants, to support replacement of dtoa_g() with +// higher-precision variants like dtoa_g_p8() or full-blown Ryu. + +// -0.000123456 +// -1.23456e-38 +CONSTI32(kMaxFloatGSlen, 12); + +CONSTI32(kMaxDoubleGSlen, 13); + +// 1.23456e-2147483647 +CONSTI32(kMaxLnGSlen, 19); + char* dtoa_g(double dxx, char* start); // We try to avoid micromanaging floating point printing and just use %g @@ -1420,6 +1448,12 @@ *penult = extra_char; return &(penult[1]); } + +HEADER_INLINE char* i32toa_x(int32_t ii, char extra_char, char* start) { + char* penult = i32toa(ii, start); + *penult = extra_char; + return &(penult[1]); +} // overread must be ok. diff -Nru plink2-2.00~a3-200116+dfsg/include/plink2_text.h plink2-2.00~a3-200217+dfsg/include/plink2_text.h --- plink2-2.00~a3-200116+dfsg/include/plink2_text.h 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/include/plink2_text.h 2020-02-14 03:51:55.000000000 +0000 @@ -115,8 +115,6 @@ namespace plink2 { #endif -extern uint32_t g_debug_print; - PglErr GetFileType(const char* fname, FileCompressionType* ftype_ptr); typedef struct TextFileBaseStruct { diff -Nru plink2-2.00~a3-200116+dfsg/plink2_adjust.cc plink2-2.00~a3-200217+dfsg/plink2_adjust.cc --- plink2-2.00~a3-200116+dfsg/plink2_adjust.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_adjust.cc 2020-02-03 03:37:41.000000000 +0000 @@ -803,12 +803,9 @@ } if (chr_ids) { const uint32_t cur_slen = token_slens[0]; - if (unlikely(cur_slen >= S_CAST(uintptr_t, tmp_alloc_end - tmp_alloc_base))) { + if (StoreStringAtBase(tmp_alloc_end, token_ptrs[0], cur_slen, &tmp_alloc_base, &(chr_ids[variant_idx]))) { goto AdjustFile_ret_NOMEM; } - chr_ids[variant_idx] = R_CAST(char*, tmp_alloc_base); - memcpyx(tmp_alloc_base, token_ptrs[0], cur_slen, '\0'); - tmp_alloc_base = &(tmp_alloc_base[cur_slen + 1]); } if (variant_bps) { if (unlikely(ScanUintDefcap(token_ptrs[1], &(variant_bps[variant_idx])))) { @@ -817,20 +814,14 @@ } } const uint32_t id_slen = token_slens[2]; - if (unlikely(id_slen >= S_CAST(uintptr_t, tmp_alloc_end - tmp_alloc_base))) { + if (StoreStringAtBase(tmp_alloc_end, token_ptrs[2], id_slen, &tmp_alloc_base, &(variant_ids[variant_idx]))) { goto AdjustFile_ret_NOMEM; } - variant_ids[variant_idx] = R_CAST(char*, tmp_alloc_base); - memcpyx(tmp_alloc_base, token_ptrs[2], id_slen, '\0'); - tmp_alloc_base = &(tmp_alloc_base[id_slen + 1]); if (need_ref) { const uint32_t cur_slen = token_slens[3]; - if (unlikely(cur_slen >= S_CAST(uintptr_t, tmp_alloc_end - tmp_alloc_base))) { + if (StoreStringAtBase(tmp_alloc_end, token_ptrs[3], cur_slen, &tmp_alloc_base, &(allele_storage[2 * variant_idx]))) { goto AdjustFile_ret_NOMEM; } - allele_storage[2 * variant_idx] = R_CAST(char*, tmp_alloc_base); - memcpyx(tmp_alloc_base, token_ptrs[3], cur_slen, '\0'); - tmp_alloc_base = &(tmp_alloc_base[cur_slen + 1]); } if (need_alt) { const char* alt_str = token_ptrs[4]; @@ -841,21 +832,15 @@ cur_slen = alt_comma - alt_str; } } - if (unlikely(cur_slen >= S_CAST(uintptr_t, tmp_alloc_end - tmp_alloc_base))) { + if (StoreStringAtBase(tmp_alloc_end, alt_str, cur_slen, &tmp_alloc_base, &(allele_storage[2 * variant_idx + 1]))) { goto AdjustFile_ret_NOMEM; } - allele_storage[2 * variant_idx + 1] = R_CAST(char*, tmp_alloc_base); - memcpyx(tmp_alloc_base, alt_str, cur_slen, '\0'); - tmp_alloc_base = &(tmp_alloc_base[cur_slen + 1]); } if (check_a1) { const uint32_t cur_slen = token_slens[5]; - if (unlikely(cur_slen >= S_CAST(uintptr_t, tmp_alloc_end - tmp_alloc_base))) { + if (StoreStringAtBase(tmp_alloc_end, token_ptrs[5], cur_slen, &tmp_alloc_base, &(a1_storage[variant_idx]))) { goto AdjustFile_ret_NOMEM; } - a1_storage[variant_idx] = R_CAST(char*, tmp_alloc_base); - memcpyx(tmp_alloc_base, token_ptrs[5], cur_slen, '\0'); - tmp_alloc_base = &(tmp_alloc_base[cur_slen + 1]); } const char* pval_str = token_ptrs[7]; double ln_pval; diff -Nru plink2-2.00~a3-200116+dfsg/plink2.cc plink2-2.00~a3-200217+dfsg/plink2.cc --- plink2-2.00~a3-200116+dfsg/plink2.cc 2020-01-17 05:21:19.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2.cc 2020-02-18 05:01:05.000000000 +0000 @@ -66,7 +66,7 @@ #ifdef USE_MKL " Intel" #endif - " (16 Jan 2020)"; + " (17 Feb 2020)"; static const char ver_str2[] = // include leading space if day < 10, so character length stays the same "" @@ -371,6 +371,7 @@ uint32_t keep_col_match_num; uint32_t filter_min_allele_ct; uint32_t filter_max_allele_ct; + uint32_t bed_border_bp; char* var_filter_exceptions_flattened; char* varid_template_str; @@ -451,7 +452,9 @@ // MajAllelesAreNeeded() uint32_t IndecentAlleleFreqsAreNeeded(Command1Flags command_flags1, double min_maf, double max_maf) { // Vscore could go either here or in the decent bucket - return (command_flags1 & (kfCommand1AlleleFreq | kfCommand1Vscore)) || (min_maf != 0.0) || (max_maf != 1.0); + // bugfix (5 Feb 2020): --freq doesn't refer to allele_freqs at all at this + // point + return (command_flags1 & kfCommand1Vscore) || (min_maf != 0.0) || (max_maf != 1.0); } @@ -469,22 +472,22 @@ return 0x7fffffff; } -uint32_t AlleleDosagesAreNeeded(MiscFlags misc_flags, uint32_t afreq_needed, uint64_t min_allele_ddosage, uint64_t max_allele_ddosage, uint32_t* regular_freqcounts_neededp) { +uint32_t AlleleDosagesAreNeeded(Command1Flags command_flags1, MiscFlags misc_flags, uint32_t afreq_needed, uint64_t min_allele_ddosage, uint64_t max_allele_ddosage, uint32_t* regular_freqcounts_neededp) { if (!(misc_flags & kfMiscNonfounders)) { return 0; } - if ((misc_flags & kfMiscMajRef) || min_allele_ddosage || (max_allele_ddosage != UINT32_MAX)) { + if ((command_flags1 & kfCommand1AlleleFreq) || (misc_flags & kfMiscMajRef) || min_allele_ddosage || (max_allele_ddosage != UINT32_MAX)) { *regular_freqcounts_neededp = 1; return 1; } return afreq_needed; } -uint32_t FounderAlleleDosagesAreNeeded(MiscFlags misc_flags, uint32_t afreq_needed, uint64_t min_allele_ddosage, uint64_t max_allele_ddosage, uint32_t* regular_freqcounts_neededp) { +uint32_t FounderAlleleDosagesAreNeeded(Command1Flags command_flags1, MiscFlags misc_flags, uint32_t afreq_needed, uint64_t min_allele_ddosage, uint64_t max_allele_ddosage, uint32_t* regular_freqcounts_neededp) { if (misc_flags & kfMiscNonfounders) { return 0; } - if ((misc_flags & kfMiscMajRef) || min_allele_ddosage || (max_allele_ddosage != (~0LLU))) { + if ((command_flags1 & kfCommand1AlleleFreq) || (misc_flags & kfMiscMajRef) || min_allele_ddosage || (max_allele_ddosage != (~0LLU))) { *regular_freqcounts_neededp = 1; return 1; } @@ -518,8 +521,7 @@ } uint32_t InfoReloadIsNeeded(Command1Flags command_flags1, PvarPsamFlags pvar_psam_flags, ExportfFlags exportf_flags, RmDupMode rmdup_mode) { - // add kfExportfBcf later - return ((command_flags1 & kfCommand1MakePlink2) && (pvar_psam_flags & kfPvarColXinfo)) || ((command_flags1 & kfCommand1Exportf) && (exportf_flags & kfExportfVcf)) || (rmdup_mode != kRmDup0); + return ((command_flags1 & kfCommand1MakePlink2) && (pvar_psam_flags & kfPvarColXinfo)) || ((command_flags1 & kfCommand1Exportf) && (exportf_flags & (kfExportfVcf | kfExportfBcf))) || (rmdup_mode != kRmDup0); } uint32_t GrmKeepIsNeeded(Command1Flags command_flags1, PcaFlags pca_flags) { @@ -592,7 +594,7 @@ } } -PglErr ApplyVariantBpFilters(const char* extract_fnames, const char* extract_intersect_fnames, const char* exclude_fnames, const ChrInfo* cip, const uint32_t* variant_bps, int32_t from_bp, int32_t to_bp, uint32_t raw_variant_ct, FilterFlags filter_flags, UnsortedVar vpos_sortstatus, uint32_t max_thread_ct, uintptr_t* variant_include, uint32_t* variant_ct_ptr) { +PglErr ApplyVariantBpFilters(const char* extract_fnames, const char* extract_intersect_fnames, const char* exclude_fnames, const ChrInfo* cip, const uint32_t* variant_bps, int32_t from_bp, int32_t to_bp, uint32_t bed_border_bp, uint32_t raw_variant_ct, FilterFlags filter_flags, UnsortedVar vpos_sortstatus, uint32_t max_thread_ct, uintptr_t* variant_include, uint32_t* variant_ct_ptr) { if (!(*variant_ct_ptr)) { return kPglRetSuccess; } @@ -630,7 +632,7 @@ logerrputs("Error: '--extract bed0'/'--extract bed1' requires a sorted .pvar/.bim. Retry\nthis command after using --make-pgen/--make-bed + --sort-vars to sort your\ndata.\n"); return kPglRetInconsistentInput; } - PglErr reterr = ExtractExcludeRange(extract_fnames, cip, variant_bps, raw_variant_ct, kVfilterExtract, (filter_flags / kfFilterExtractBed0) & 1, max_thread_ct, variant_include, variant_ct_ptr); + PglErr reterr = ExtractExcludeRange(extract_fnames, cip, variant_bps, raw_variant_ct, kVfilterExtract, (filter_flags / kfFilterExtractBed0) & 1, bed_border_bp, max_thread_ct, variant_include, variant_ct_ptr); if (unlikely(reterr)) { return reterr; } @@ -640,7 +642,7 @@ logerrputs("Error: '--extract-intersect bed0'/'--extract-intersect bed1' requires a sorted\n.pvar/.bim. Retry this command after using --make-pgen/--make-bed +\n--sort-vars to sort your data.\n"); return kPglRetInconsistentInput; } - PglErr reterr = ExtractExcludeRange(extract_intersect_fnames, cip, variant_bps, raw_variant_ct, kVfilterExtractIntersect, (filter_flags / kfFilterExtractIntersectBed0) & 1, max_thread_ct, variant_include, variant_ct_ptr); + PglErr reterr = ExtractExcludeRange(extract_intersect_fnames, cip, variant_bps, raw_variant_ct, kVfilterExtractIntersect, (filter_flags / kfFilterExtractIntersectBed0) & 1, bed_border_bp, max_thread_ct, variant_include, variant_ct_ptr); if (unlikely(reterr)) { return reterr; } @@ -650,7 +652,7 @@ logerrputs("Error: '--exclude bed0'/'--exclude bed1' requires a sorted .pvar/.bim. Retry\nthis commandafter using --make-pgen/--make-bed + --sort-vars to sort your\ndata.\n"); return kPglRetInconsistentInput; } - PglErr reterr = ExtractExcludeRange(exclude_fnames, cip, variant_bps, raw_variant_ct, kVfilterExclude, (filter_flags / kfFilterExcludeBed0) & 1, max_thread_ct, variant_include, variant_ct_ptr); + PglErr reterr = ExtractExcludeRange(exclude_fnames, cip, variant_bps, raw_variant_ct, kVfilterExclude, (filter_flags / kfFilterExcludeBed0) & 1, bed_border_bp, max_thread_ct, variant_include, variant_ct_ptr); if (unlikely(reterr)) { return reterr; } @@ -853,7 +855,7 @@ // LoadPvar() uses pvar_psam_flags to determine what's needed for .pvar // export. These booleans are just for tracking requirements beyond // that. - const uint32_t xheader_needed = (pcp->exportf_info.flags & kfExportfVcf)? 1 : 0; + const uint32_t xheader_needed = (pcp->exportf_info.flags & (kfExportfVcf | kfExportfBcf))? 1 : 0; const uint32_t qualfilter_needed = xheader_needed || ((pcp->rmdup_mode != kRmDup0) && (pcp->rmdup_mode <= kRmDupExcludeMismatch)); reterr = LoadPvar(pvarname, pcp->var_filter_exceptions_flattened, pcp->varid_template_str, pcp->varid_multi_template_str, pcp->varid_multi_nonsnp_template_str, pcp->missing_varid_match, pcp->require_info_flattened, pcp->require_no_info_flattened, &(pcp->extract_if_info_expr), &(pcp->exclude_if_info_expr), pcp->misc_flags, pcp->pvar_psam_flags, xheader_needed, qualfilter_needed, pcp->var_min_qual, pcp->splitpar_bound1, pcp->splitpar_bound2, pcp->new_variant_id_max_allele_slen, (pcp->filter_flags / kfFilterSnpsOnly) & 3, !(pcp->dependency_flags & kfFilterNoSplitChr), pcp->filter_min_allele_ct, pcp->filter_max_allele_ct, pcp->max_thread_ct, cip, &max_variant_id_slen, &info_reload_slen, &vpos_sortstatus, &xheader, &variant_include, &variant_bps, &variant_ids_mutable, &allele_idx_offsets, K_CAST(const char***, &allele_storage_mutable), &pvar_qual_present, &pvar_quals, &pvar_filter_present, &pvar_filter_npass, &pvar_filter_storage_mutable, &nonref_flags, &variant_cms, &chr_idxs, &raw_variant_ct, &variant_ct, &max_allele_ct, &max_allele_slen, &xheader_blen, &info_flags, &max_filter_slen); @@ -1159,7 +1161,7 @@ const uint32_t htable_needed_early = variant_ct && (pcp->varid_from || pcp->varid_to || pcp->varid_snp || pcp->varid_exclude_snp || pcp->snps_range_list.name_ct || pcp->exclude_snps_range_list.name_ct); const uint32_t full_variant_id_htable_needed = variant_ct && (htable_needed_early || pcp->update_map_flag || pcp->update_name_flag || pcp->update_alleles_fname || (pcp->rmdup_mode != kRmDup0) || pcp->extract_col_cond_info.params); if (!full_variant_id_htable_needed) { - reterr = ApplyVariantBpFilters(pcp->extract_fnames, pcp->extract_intersect_fnames, pcp->exclude_fnames, cip, variant_bps, pcp->from_bp, pcp->to_bp, raw_variant_ct, pcp->filter_flags, vpos_sortstatus, pcp->max_thread_ct, variant_include, &variant_ct); + reterr = ApplyVariantBpFilters(pcp->extract_fnames, pcp->extract_intersect_fnames, pcp->exclude_fnames, cip, variant_bps, pcp->from_bp, pcp->to_bp, pcp->bed_border_bp, raw_variant_ct, pcp->filter_flags, vpos_sortstatus, pcp->max_thread_ct, variant_include, &variant_ct); if (unlikely(reterr)) { goto Plink2Core_ret_1; } @@ -1292,7 +1294,7 @@ BigstackReset(bigstack_mark); if (full_variant_id_htable_needed) { - reterr = ApplyVariantBpFilters(pcp->extract_fnames, pcp->extract_intersect_fnames, pcp->exclude_fnames, cip, variant_bps, pcp->from_bp, pcp->to_bp, raw_variant_ct, pcp->filter_flags, vpos_sortstatus, pcp->max_thread_ct, variant_include, &variant_ct); + reterr = ApplyVariantBpFilters(pcp->extract_fnames, pcp->extract_intersect_fnames, pcp->exclude_fnames, cip, variant_bps, pcp->from_bp, pcp->to_bp, pcp->bed_border_bp, raw_variant_ct, pcp->filter_flags, vpos_sortstatus, pcp->max_thread_ct, variant_include, &variant_ct); if (unlikely(reterr)) { goto Plink2Core_ret_1; } @@ -1806,18 +1808,18 @@ } bigstack_mark_allele_ddosages = g_bigstack_base; uint32_t regular_freqcounts_needed = (allele_presents != nullptr); - if (AlleleDosagesAreNeeded(pcp->misc_flags, (allele_freqs != nullptr), pcp->min_allele_ddosage, pcp->max_allele_ddosage, ®ular_freqcounts_needed)) { + if (AlleleDosagesAreNeeded(pcp->command_flags1, pcp->misc_flags, (allele_freqs != nullptr), pcp->min_allele_ddosage, pcp->max_allele_ddosage, ®ular_freqcounts_needed)) { if (unlikely(bigstack_alloc_u64(raw_allele_ct, &allele_ddosages))) { goto Plink2Core_ret_NOMEM; } } - if (FounderAlleleDosagesAreNeeded(pcp->misc_flags, (allele_freqs != nullptr), pcp->min_allele_ddosage, pcp->max_allele_ddosage, ®ular_freqcounts_needed)) { - if ((founder_ct == sample_ct) && allele_ddosages) { - founder_allele_ddosages = allele_ddosages; - } else { - if (unlikely(bigstack_alloc_u64(raw_allele_ct, &founder_allele_ddosages))) { - goto Plink2Core_ret_NOMEM; - } + if (FounderAlleleDosagesAreNeeded(pcp->command_flags1, pcp->misc_flags, (allele_freqs != nullptr), pcp->min_allele_ddosage, pcp->max_allele_ddosage, ®ular_freqcounts_needed)) { + // For now, we never need allele_ddosages and founder_allele_ddosages + // at the same time. If we ever do, we should just set + // founder_allele_ddosages = allele_ddosages when the latter is + // allocated and founder_ct == sample_ct. + if (unlikely(bigstack_alloc_u64(raw_allele_ct, &founder_allele_ddosages))) { + goto Plink2Core_ret_NOMEM; } } double* imp_r2_vals = nullptr; @@ -2753,7 +2755,7 @@ } else { if (unlikely(ScanUintDefcapx(sources[3], &((*tcbuf)->skip_ct)))) { Alloc2col_invalid_skip: - logerrprintf("Error: Invalid --%s skip parameter. This needs to either be a\nsingle character (usually '#') which, when present at the start of a line,\nindicates it should be skipped; or the number of initial lines to skip. (Note\nthat in shells such as bash, '#' is a special character that must be\nsurrounded by single- or double-quotes to be parsed correctly.)\n", flagname_p); + logerrprintfww("Error: Invalid --%s skip argument. This needs to either be a single character (usually '#') which, when present at the start of a line, indicates it should be skipped; or the number of initial lines to skip. (Note that in shells such as bash, '#' is a special character that must be surrounded by single- or double-quotes to be parsed correctly.)\n", flagname_p); return kPglRetInvalidCmdline; } } @@ -2832,11 +2834,11 @@ return cip->incl_excl_name_stack && (!(cip->incl_excl_name_stack->next)); } -void GetExportfTargets(const char* const* argvk, uint32_t param_ct, ExportfFlags* exportf_flags_ptr, IdpasteFlags* exportf_id_paste_ptr, uint32_t* format_param_idxs_ptr) { +void GetExportfTargets(const char* const* argvk, uint32_t param_ct, ExportfFlags* exportf_flags_ptr, IdpasteFlags* exportf_id_paste_ptr, uint64_t* format_param_idxs_ptr) { // does not error out if no format present, since this is needed for --recode // translation // supports multiple formats - uint32_t format_param_idxs = 0; + uint64_t format_param_idxs = 0; for (uint32_t param_idx = 1; param_idx <= param_ct; ++param_idx) { const char* cur_modif = argvk[param_idx]; const char* cur_modif2 = &(cur_modif[1]); @@ -2860,7 +2862,11 @@ case 'b': { const uint32_t cur_modif2_slen = strlen(cur_modif2); - if (strequal_k(cur_modif2, "eagle", cur_modif2_slen)) { + if (strequal_k(cur_modif2, "cf", cur_modif2_slen)) { + cur_format = kfExportfBcf43; + } else if (strequal_k(cur_modif2, "cf-4.2", cur_modif2_slen)) { + cur_format = kfExportfBcf42; + } else if (strequal_k(cur_modif2, "eagle", cur_modif2_slen)) { cur_format = kfExportfBeagle; } else if (strequal_k(cur_modif2, "eagle-nomap", cur_modif2_slen)) { cur_format = kfExportfBeagleNomap; @@ -2967,7 +2973,7 @@ break; } if (cur_format) { - format_param_idxs |= 1U << param_idx; + format_param_idxs |= 1LLU << param_idx; *exportf_flags_ptr |= cur_format; } } @@ -3074,7 +3080,7 @@ strequal_k(argv1, "--zd", argv1_slen) || strequal_k(argv1, "-zd", argv1_slen)) { if (unlikely(argc == 2)) { - fprintf(stderr, "Error: Missing %s parameter.\n", argv[1]); + fprintf(stderr, "Error: Missing %s argument.\n", argv[1]); return S_CAST(uint32_t, kPglRetInvalidCmdline); } for (int ii = 2; ii != argc; ++ii) { @@ -3084,7 +3090,7 @@ } } if (unlikely(argc > 4)) { - fprintf(stderr, "Error: %s accepts at most 2 parameters.\n", argv[1]); + fprintf(stderr, "Error: %s accepts at most 2 arguments.\n", argv[1]); return S_CAST(uint32_t, kPglRetInvalidCmdline); } return S_CAST(uint32_t, ZstDecompress(argv[2], (argc == 4)? argv[3] : nullptr)); @@ -3381,12 +3387,12 @@ // special case: translate to "export ped" if no format specified const uint32_t param_ct = GetParamCt(argvk, argc, arg_idx); if (unlikely(param_ct > 4)) { - fputs("Error: --recode accepts at most 4 parameters.\n", stderr); + fputs("Error: --recode accepts at most 4 arguments.\n", stderr); goto main_ret_INVALID_CMDLINE; } ExportfFlags dummy; IdpasteFlags dummy2; - uint32_t format_param_idxs; + uint64_t format_param_idxs; GetExportfTargets(&(argvk[arg_idx]), param_ct, &dummy, &dummy2, &format_param_idxs); if (!format_param_idxs) { snprintf(flagname_write_iter, kMaxFlagBlen, "export ped"); @@ -3542,6 +3548,7 @@ pc.keep_col_match_num = 0; pc.filter_min_allele_ct = 0; pc.filter_max_allele_ct = UINT32_MAX; + pc.bed_border_bp = 0; double import_dosage_certainty = 0.0; int32_t vcf_min_gq = -1; int32_t vcf_min_dp = -1; @@ -3590,7 +3597,7 @@ if (param_ct) { const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(!strequal_k_unsafe(cur_modif, "0"))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --allow-extra-chr parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --allow-extra-chr argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } chr_info.zero_extra_chrs = 1; @@ -3640,7 +3647,7 @@ goto main_ret_1; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --adjust parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --adjust argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -3681,7 +3688,7 @@ } else if (likely(strequal_k(cur_modif, "input-log10", cur_modif_slen))) { adjust_file_info.base.flags |= kfAdjustInputLog10; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --adjust-file parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --adjust-file argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -3833,7 +3840,7 @@ const char* cur_modif = argvk[arg_idx + 1]; uint32_t autosome_ct; if (unlikely(ScanPosintCappedx(cur_modif, kMaxChrTextnum, &autosome_ct))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --autosome-num parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --autosome-num argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } // see plink2_common FinalizeChrset() @@ -3856,7 +3863,7 @@ if (!strcmp(sources[0], "force")) { --param_ct; if (unlikely(!param_ct)) { - logerrputs("Error: Invalid --alt1-allele parameter sequence.\n"); + logerrputs("Error: Invalid --alt1-allele argument sequence.\n"); goto main_ret_INVALID_CMDLINE_A; } pc.misc_flags |= kfMiscAlt1AlleleForce; @@ -3898,7 +3905,7 @@ // avoid buffer overflow in the case we rename and append '~'. if (unlikely(slen > (kPglFnamesize - 10))) { // could use kPglFnamesize - 2 - 3 * param_ct, but that's pointless - logerrputs("Error: --bfile parameter too long.\n"); + logerrputs("Error: --bfile argument too long.\n"); goto main_ret_OPEN_FAIL; } snprintf(memcpya(pgenname, fname_prefix, slen), 9, ".bed"); @@ -3926,7 +3933,7 @@ const char* fname_prefix = argvk[arg_idx + fname_modif_idx]; const uint32_t slen = strlen(fname_prefix); if (unlikely(slen > (kPglFnamesize - 10))) { - logerrputs("Error: --bpfile parameter too long.\n"); + logerrputs("Error: --bpfile argument too long.\n"); goto main_ret_OPEN_FAIL; } snprintf(memcpya(pgenname, fname_prefix, slen), 9, ".pgen"); @@ -3948,7 +3955,7 @@ const char* cur_modif = argvk[arg_idx + 2]; const uint32_t cur_modif_slen = strlen(cur_modif); if (unlikely(!StrStartsWith(cur_modif, "dosage=", cur_modif_slen))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --bcf parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --bcf argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } reterr = CmdlineAllocString(&(cur_modif[strlen("dosage=")]), argvk[arg_idx], 4095, &vcf_dosage_import_field); @@ -3956,11 +3963,16 @@ goto main_ret_1; } if (unlikely(!((!strcmp(vcf_dosage_import_field, "GP-force")) || IsAlphanumeric(vcf_dosage_import_field)))) { - logerrputs("Error: --bcf dosage= parameter is not alphanumeric.\n"); + logerrputs("Error: --bcf dosage= argument is not alphanumeric.\n"); goto main_ret_INVALID_CMDLINE; } if (unlikely(!strcmp(vcf_dosage_import_field, "GT"))) { - logerrputs("Error: --bcf dosage= parameter cannot be 'GT'.\n"); + logerrputs("Error: --bcf dosage= argument cannot be 'GT'.\n"); + goto main_ret_INVALID_CMDLINE; + } + // rather not worry about string-index 0 here + if (unlikely(!strcmp(vcf_dosage_import_field, "PASS"))) { + logerrputs("Error: --bcf dosage= argument cannot be 'PASS'.\n"); goto main_ret_INVALID_CMDLINE; } } @@ -3998,7 +4010,7 @@ logerrputs("Warning: --bgen 'ref-second' modifier is deprecated. Use 'ref-last' instead.\n"); oxford_import_flags |= kfOxfordImportRefLast; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --bgen parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --bgen argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -4023,6 +4035,38 @@ goto main_ret_INVALID_CMDLINE_WWA; } pc.filter_flags |= kfFilterPvarReq | kfFilterNoSplitChr; + } else if (strequal_k_unsafe(flagname_p2, "ed-border-bp")) { + if (unlikely(EnforceParamCtRange(argvk[arg_idx], param_ct, 1, 1))) { + goto main_ret_INVALID_CMDLINE_2A; + } + double dxx; + if (unlikely((!ScantokDouble(argvk[arg_idx + 1], &dxx)) || (dxx < 0.0))) { + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --bed-border-bp argument '%s'.\n", argvk[arg_idx + 1]); + goto main_ret_INVALID_CMDLINE_WWA; + } + if (dxx > 2147483646) { + pc.bed_border_bp = 0x7ffffffe; + } else { + pc.bed_border_bp = S_CAST(int32_t, dxx * (1 + kSmallEpsilon)); + } + } else if (strequal_k_unsafe(flagname_p2, "ed-border-kb")) { + if (unlikely(pc.bed_border_bp)) { + logerrputs("Error: --bed-border-kb cannot be used with --bed-border-bp.\n"); + goto main_ret_INVALID_CMDLINE; + } + if (unlikely(EnforceParamCtRange(argvk[arg_idx], param_ct, 1, 1))) { + goto main_ret_INVALID_CMDLINE_2A; + } + double dxx; + if (unlikely((!ScantokDouble(argvk[arg_idx + 1], &dxx)) || (dxx < 0.0))) { + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --bed-border-kb argument '%s'.\n", argvk[arg_idx + 1]); + goto main_ret_INVALID_CMDLINE_WWA; + } + if (dxx > 2147483.646) { + pc.bed_border_bp = 0x7ffffffe; + } else { + pc.bed_border_bp = S_CAST(int32_t, dxx * 1000 * (1 + kSmallEpsilon)); + } } else if (strequal_k_unsafe(flagname_p2, "ad-ld")) { pc.misc_flags |= kfMiscAllowBadLd; goto main_param_zero; @@ -4096,7 +4140,7 @@ goto main_ret_INVALID_CMDLINE_2A; } if (unlikely(!ScantokDouble(argvk[arg_idx + 1], &pc.ci_size))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --ci parameter '%s'.\n", argvk[arg_idx + 1]); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --ci argument '%s'.\n", argvk[arg_idx + 1]); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely((pc.ci_size < 0.01) || (pc.ci_size >= 1.0))) { @@ -4123,7 +4167,7 @@ strequal_k(cur_modif, "m", cur_modif_slen))) { pc.glm_info.flags |= kfGlmConditionMultiallelic; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --condition parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --condition argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -4155,7 +4199,7 @@ strequal_k(cur_modif, "m", cur_modif_slen))) { pc.glm_info.flags |= kfGlmConditionMultiallelic; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --condition-list parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --condition-list argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -4196,7 +4240,7 @@ const char* cur_modif = argvk[arg_idx + 1]; int32_t signed_autosome_ct; if (unlikely(ScanIntAbsBoundedx(cur_modif, kMaxChrTextnum, &signed_autosome_ct) || (!signed_autosome_ct))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --chr-set parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --chr-set argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } // see plink2_common FinalizeChrset() @@ -4205,7 +4249,7 @@ if (signed_autosome_ct < 0) { // haploid if (unlikely(param_ct > 1)) { - logerrputs("Error: --chr-set does not accept multiple parameters in haploid mode.\n"); + logerrputs("Error: --chr-set does not accept multiple arguments in haploid mode.\n"); goto main_ret_INVALID_CMDLINE_A; } const uint32_t autosome_ct = -signed_autosome_ct; @@ -4241,7 +4285,7 @@ chr_info.xymt_codes[3] = UINT32_MAXM1; ClearBit(autosome_ct + 4, chr_info.haploid_mask); } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --chr-set parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --chr-set argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -4255,7 +4299,7 @@ if (likely(!strcmp(cur_modif, "file"))) { pc.misc_flags |= kfMiscChrOverrideFile; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --chr-override parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --chr-override argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else { @@ -4328,7 +4372,7 @@ } is_gzs = 1; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --data parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --data argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -4338,7 +4382,7 @@ const char* fname_prefix = argvk[arg_idx + 1]; const uint32_t slen = strlen(fname_prefix); if (unlikely(slen > (kPglFnamesize - 9))) { - logerrputs("Error: --data parameter too long.\n"); + logerrputs("Error: --data argument too long.\n"); goto main_ret_INVALID_CMDLINE; } if (!(xload & kfXloadOxBgen)) { @@ -4359,7 +4403,7 @@ const char* cur_modif = argvk[arg_idx + 1]; double dosage_erase_frac; if (unlikely((!ScantokDouble(cur_modif, &dosage_erase_frac)) || (dosage_erase_frac < 0.0) || (dosage_erase_frac >= (0.5 - kSmallEpsilon)))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --dosage-erase-threshold parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --dosage-erase-threshold argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.dosage_erase_thresh = S_CAST(int32_t, dosage_erase_frac * ((1 + kSmallEpsilon) * kDosageMid)); @@ -4391,7 +4435,7 @@ } else if (StrStartsWith(cur_modif, "pheno-ct=", cur_modif_slen)) { const char* pheno_ct_start = &(cur_modif[strlen("pheno-ct=")]); if (unlikely(ScanUintCappedx(pheno_ct_start, kMaxPhenoCt, &gendummy_info.pheno_ct))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --dummy pheno-ct= parameter '%s'.\n", pheno_ct_start); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --dummy pheno-ct= argument '%s'.\n", pheno_ct_start); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k(cur_modif, "scalar-pheno", cur_modif_slen)) { @@ -4400,14 +4444,14 @@ const char* dosage_freq_start = &(cur_modif[strlen("dosage-freq=")]); double dxx; if (unlikely((!ScantokDouble(dosage_freq_start, &dxx)) || (dxx < 0.0) || (dxx > 1.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --dummy dosage-freq= parameter '%s'.\n", dosage_freq_start); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --dummy dosage-freq= argument '%s'.\n", dosage_freq_start); goto main_ret_INVALID_CMDLINE_WWA; } gendummy_info.dosage_freq = dxx; } else { double dxx; if (unlikely((extra_numeric_param_ct == 2) || (!ScantokDouble(cur_modif, &dxx)) || (dxx < 0.0) || (dxx > 1.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --dummy parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --dummy argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (!extra_numeric_param_ct) { @@ -4543,7 +4587,7 @@ if (unlikely(EnforceParamCtRange(argvk[arg_idx], param_ct, 0, 50))) { goto main_ret_INVALID_CMDLINE_2A; } - uint32_t format_param_idxs = 0; + uint64_t format_param_idxs = 0; if (!flagname_p2[5]) { GetExportfTargets(&(argvk[arg_idx]), param_ct, &pc.exportf_info.flags, &pc.exportf_info.idpaste_flags, &format_param_idxs); if (unlikely(!format_param_idxs)) { @@ -4577,9 +4621,8 @@ const char* cur_modif = argvk[arg_idx + param_idx]; const uint32_t cur_modif_slen = strlen(cur_modif); if (StrStartsWith(cur_modif, "id-paste=", cur_modif_slen)) { - if (unlikely(!(pc.exportf_info.flags & (kfExportfVcf | kfExportfBgen12 | kfExportfBgen13)))) { - // todo: bcf - logerrputs("Error: The 'id-paste' modifier only applies to --export's vcf, bgen-1.2, and\nbgen-1.3 output formats.\n"); + if (unlikely(!(pc.exportf_info.flags & (kfExportfVcf | kfExportfBcf | kfExportfBgen12 | kfExportfBgen13)))) { + logerrputs("Error: The 'id-paste' modifier only applies to --export's vcf, bcf, bgen-1.2,\nand bgen-1.3 output formats.\n"); goto main_ret_INVALID_CMDLINE_A; } if (unlikely(pc.exportf_info.idpaste_flags)) { @@ -4591,8 +4634,8 @@ goto main_ret_1; } } else if (StrStartsWith(cur_modif, "id-delim=", cur_modif_slen)) { - if (unlikely(!(pc.exportf_info.flags & (kfExportfVcf | kfExportfBgen12 | kfExportfBgen13)))) { - logerrputs("Error: The 'id-delim' modifier only applies to --export's vcf, bgen-1.2, and\nbgen-1.3 output formats.\n"); + if (unlikely(!(pc.exportf_info.flags & (kfExportfVcf | kfExportfBcf | kfExportfBgen12 | kfExportfBgen13)))) { + logerrputs("Error: The 'id-delim' modifier only applies to --export's vcf, bcf, bgen-1.2,\nand bgen-1.3 output formats.\n"); goto main_ret_INVALID_CMDLINE_A; } if (unlikely(pc.exportf_info.id_delim)) { @@ -4609,8 +4652,8 @@ goto main_ret_INVALID_CMDLINE; } } else if (StrStartsWith(cur_modif, "vcf-dosage=", cur_modif_slen)) { - if (unlikely(!(pc.exportf_info.flags & kfExportfVcf))) { - logerrputs("Error: The 'vcf-dosage' modifier only applies to --export's vcf output format.\n"); + if (unlikely(!(pc.exportf_info.flags & (kfExportfVcf | kfExportfBcf)))) { + logerrputs("Error: The 'vcf-dosage' modifier only applies to --export's vcf and bcf output\nformats.\n"); goto main_ret_INVALID_CMDLINE_A; } if (unlikely(pc.exportf_info.vcf_mode != kVcfExport0)) { @@ -4620,8 +4663,8 @@ const char* vcf_dosage_start = &(cur_modif[strlen("vcf-dosage=")]); const uint32_t vcf_dosage_start_slen = strlen(vcf_dosage_start); if (strequal_k(vcf_dosage_start, "GP", vcf_dosage_start_slen)) { - if (pc.exportf_info.flags & kfExportfVcf42) { - logerrputs("Error: --export vcf-dosage=GP cannot be used in vcf-4.2 mode.\n"); + if (pc.exportf_info.flags & (kfExportfVcf42 | kfExportfBcf42)) { + logerrputs("Error: --export vcf-dosage=GP cannot be used in {v,b}cf-4.2 mode.\n"); goto main_ret_INVALID_CMDLINE_A; } pc.exportf_info.vcf_mode = kVcfExportGp; @@ -4634,7 +4677,7 @@ } else if (likely(strequal_k(vcf_dosage_start, "HDS-force", vcf_dosage_start_slen))) { pc.exportf_info.vcf_mode = kVcfExportHdsForce; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --export vcf-dosage= parameter '%s'.\n", vcf_dosage_start); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --export vcf-dosage= argument '%s'.\n", vcf_dosage_start); goto main_ret_INVALID_CMDLINE_WWA; } } else if (StrStartsWith(cur_modif, "bits=", cur_modif_slen)) { @@ -4649,7 +4692,7 @@ const char* bits_start = &(cur_modif[strlen("bits=")]); uint32_t bgen_bits; if (unlikely(ScanPosintCappedx(bits_start, 24, &bgen_bits))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --export bits= parameter '%s'.\n", bits_start); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --export bits= argument '%s'.\n", bits_start); goto main_ret_INVALID_CMDLINE_WWA; } if (bgen_bits > 16) { @@ -4680,8 +4723,8 @@ snprintf(g_logbuf, kLogbufSize, "Error: The '%s' modifier does not apply to --export's A and AD output formats.\n", cur_modif); goto main_ret_INVALID_CMDLINE_2A; } - if (unlikely(pc.exportf_info.flags & kfExportfVcf)) { - logerrputs("Error: '01'/'12' cannot be used with --export's vcf output format.\n"); + if (unlikely(pc.exportf_info.flags & (kfExportfVcf | kfExportfBcf))) { + logerrputs("Error: '01'/'12' cannot be used with --export's vcf or bcf output formats.\n"); goto main_ret_INVALID_CMDLINE_A; } if (cur_modif[0] == '0') { @@ -4711,14 +4754,22 @@ logerrputs("Error: 'gen-gz' modifier retired. Use '--export oxford bgz' instead.\n"); goto main_ret_INVALID_CMDLINE_WWA; } else if (StrStartsWith(cur_modif, "dosage=", cur_modif_slen)) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --export parameter '%s'. (Did you mean 'vcf-%s'?)\n", cur_modif, cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --export argument '%s'. (Did you mean 'vcf-%s'?)\n", cur_modif, cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --export parameter '%s'.%s\n", cur_modif, ((param_idx == param_ct) && (!outname_end))? " (Did you forget '--out'?)" : ""); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --export argument '%s'.%s\n", cur_modif, ((param_idx == param_ct) && (!outname_end))? " (Did you forget '--out'?)" : ""); goto main_ret_INVALID_CMDLINE_WWA; } } - if (pc.exportf_info.flags & (kfExportfVcf | kfExportfBgen12 | kfExportfBgen13)) { + if ((pc.exportf_info.flags & kfExportfVcf) == kfExportfVcf) { + logerrputs("Error: --export 'vcf' and 'vcf-4.2' cannot be used together.\n"); + goto main_ret_INVALID_CMDLINE_A; + } + if ((pc.exportf_info.flags & kfExportfBcf) == kfExportfBcf) { + logerrputs("Error: --export 'bcf' and 'bcf-4.2' cannot be used together.\n"); + goto main_ret_INVALID_CMDLINE_A; + } + if (pc.exportf_info.flags & (kfExportfVcf | kfExportfBcf | kfExportfBgen12 | kfExportfBgen13)) { if (!pc.exportf_info.idpaste_flags) { pc.exportf_info.idpaste_flags = kfIdpasteDefault; } @@ -4812,7 +4863,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(!ScantokDouble(cur_modif, &pc.extract_col_cond_info.max))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --extract-col-cond-max parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --extract-col-cond-max argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k_unsafe(flagname_p2, "xtract-col-cond-min")) { @@ -4829,7 +4880,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(!ScantokDouble(cur_modif, &pc.extract_col_cond_info.min))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --extract-col-cond-min parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --extract-col-cond-min argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k_unsafe(flagname_p2, "xtract-if-info")) { @@ -4921,7 +4972,7 @@ goto main_ret_1; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --freq parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --freq argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -4972,7 +5023,7 @@ const char* cur_modif = argvk[arg_idx + 1]; double dxx; if (unlikely(!ScantokDouble(cur_modif, &dxx))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --from-bp/-kb/-mb parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --from-bp/-kb/-mb argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } const char unit_char = flagname_p2[4]; @@ -4988,7 +5039,7 @@ // change from v1.9) // don't use ceil() since e.g. ceil(0.001015 * 1000000) is 1016 if (unlikely(dxx > 2147483646.0)) { - logerrprintf("Error: --from-bp/-kb/-mb parameter '%s' too large.\n", cur_modif); + logerrprintf("Error: --from-bp/-kb/-mb argument '%s' too large.\n", cur_modif); goto main_ret_INVALID_CMDLINE_A; } pc.from_bp = 1 + S_CAST(int32_t, dxx * (1 - kSmallEpsilon)); @@ -5059,13 +5110,13 @@ } else if (!strcmp(cur_modif, "hh-missing")) { pc.misc_flags |= kfMiscGenoHhMissing; } else if (unlikely(geno_thresh_present)) { - logerrputs("Error: Invalid --geno parameter sequence.\n"); + logerrputs("Error: Invalid --geno argument sequence.\n"); goto main_ret_INVALID_CMDLINE_A; } else if (unlikely(!ScantokDouble(cur_modif, &pc.geno_thresh))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --geno parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --geno argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } else if (unlikely((pc.geno_thresh < 0.0) || (pc.geno_thresh > 1.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --geno parameter '%s' (must be in [0, 1]).\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --geno argument '%s' (must be in [0, 1]).\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } else { geno_thresh_present = 1; @@ -5106,7 +5157,7 @@ goto main_ret_INVALID_CMDLINE_A; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --geno-counts parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --geno-counts argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -5184,7 +5235,7 @@ goto main_ret_INVALID_CMDLINE_A; } if (unlikely(ScanPosintDefcapx(&(cur_modif[6]), &pc.glm_info.mperm_ct))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --glm mperm= parameter '%s'.\n", &(cur_modif[6])); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --glm mperm= argument '%s'.\n", &(cur_modif[6])); goto main_ret_INVALID_CMDLINE_WWA; } } else if (StrStartsWith(cur_modif, "local-covar=", cur_modif_slen)) { @@ -5272,7 +5323,7 @@ goto main_ret_INVALID_CMDLINE_A; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --glm parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --glm argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -5280,11 +5331,11 @@ pc.glm_info.cols = kfGlmColDefault; } if (unlikely((explicit_firth_fallback && (pc.glm_info.flags & (kfGlmNoFirth | kfGlmFirth))) || ((pc.glm_info.flags & (kfGlmNoFirth | kfGlmFirth)) == (kfGlmNoFirth | kfGlmFirth)))) { - logerrputs("Error: Conflicting --glm parameters.\n"); + logerrputs("Error: Conflicting --glm arguments.\n"); goto main_ret_INVALID_CMDLINE_A; } if (unlikely((pc.glm_info.flags & (kfGlmSex | kfGlmNoXSex)) == (kfGlmSex | kfGlmNoXSex))) { - logerrputs("Error: Conflicting --glm parameters.\n"); + logerrputs("Error: Conflicting --glm arguments.\n"); goto main_ret_INVALID_CMDLINE_A; } if (unlikely((pc.glm_info.flags & kfGlmPerm) && pc.glm_info.mperm_ct)) { @@ -5295,7 +5346,7 @@ if (alternate_genotype_col_flags) { pc.xchr_model = 0; if (unlikely(alternate_genotype_col_flags & (alternate_genotype_col_flags - 1))) { - logerrputs("Error: Conflicting --glm parameters.\n"); + logerrputs("Error: Conflicting --glm arguments.\n"); goto main_ret_INVALID_CMDLINE_A; } } @@ -5354,7 +5405,7 @@ strequal_k(cur_modif, "ref-second", cur_modif_slen))) { oxford_import_flags |= kfOxfordImportRefLast; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --gen parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --gen argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } const char* cur_fname = argvk[arg_idx + 1]; @@ -5372,7 +5423,7 @@ if (param_ct) { const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(strcmp("dosage", cur_modif))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --genotyping-rate parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --genotyping-rate argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.misc_flags |= kfMiscGenotypingRateDosage; @@ -5412,7 +5463,7 @@ goto main_ret_INVALID_CMDLINE_A; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --hardy parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --hardy argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -5433,7 +5484,7 @@ pc.misc_flags |= kfMiscHweKeepFewhet; } else { if (unlikely((pc.hwe_thresh != 0.0) || (!ScantokDouble(cur_modif, &pc.hwe_thresh)))) { - logerrputs("Error: Invalid --hwe parameter sequence.\n"); + logerrputs("Error: Invalid --hwe argument sequence.\n"); goto main_ret_INVALID_CMDLINE_A; } if (unlikely((pc.hwe_thresh < 0.0) || (pc.hwe_thresh >= 1.0))) { @@ -5454,7 +5505,7 @@ const char* cur_modif = argvk[arg_idx + 1]; double hard_call_frac; if (unlikely((!ScantokDouble(cur_modif, &hard_call_frac)) || (hard_call_frac < 0.0) || (hard_call_frac >= (0.5 - kSmallEpsilon)))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --hard-call-threshold parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --hard-call-threshold argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.hard_call_thresh = S_CAST(int32_t, hard_call_frac * ((1 + kSmallEpsilon) * kDosageMid)); @@ -5502,7 +5553,7 @@ strequal_k(cur_modif, "ref-second", cur_modif_slen))) { oxford_import_flags |= kfOxfordImportRefLast; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --haps parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --haps argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -5560,7 +5611,7 @@ goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely((param_ct > 1) && (!(pc.sample_sort_flags & kfSortFile)))) { - snprintf(g_logbuf, kLogbufSize, "Error: '--indiv-sort %s' does not accept additional parameters.\n", mode_str); + snprintf(g_logbuf, kLogbufSize, "Error: '--indiv-sort %s' does not accept additional arguments.\n", mode_str); goto main_ret_INVALID_CMDLINE_2A; } } else if (strequal_k_unsafe(flagname_p2, "d-delim")) { @@ -5579,7 +5630,7 @@ goto main_ret_INVALID_CMDLINE_A; } if (unlikely(ctou32(id_delim) < ' ')) { - logerrputs("Error: --id-delim parameter cannot be tab, newline, or a nonprinting character.\n"); + logerrputs("Error: --id-delim argument cannot be tab, newline, or a nonprinting character.\n"); goto main_ret_INVALID_CMDLINE; } } @@ -5628,7 +5679,7 @@ } } if (unlikely(next_param_idx + 2 == param_ct)) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --%s parameter sequence.\n", flagname_p); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --%s argument sequence.\n", flagname_p); goto main_ret_INVALID_CMDLINE_2A; } if (next_param_idx < param_ct) { @@ -5669,7 +5720,7 @@ const char* cur_modif = argvk[arg_idx + 1]; input_missing_geno_char = ExtractCharParam(cur_modif); if (unlikely(ctou32(input_missing_geno_char) <= ' ')) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --input-missing-genotype parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --input-missing-genotype argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k_unsafe(flagname_p2, "nput-missing-phenotype")) { @@ -5679,7 +5730,7 @@ const char* cur_modif = argvk[arg_idx + 1]; double dxx; if (unlikely(ScanInt32x(cur_modif, &pc.missing_pheno) || ((pc.missing_pheno >= 0) && (pc.missing_pheno <= 2)) || (!ScantokDouble(cur_modif, &dxx)) || (dxx != S_CAST(double, pc.missing_pheno)))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --input-missing-phenotype parameter '%s' (must be an integer in [-2147483647, -1] or [3, 2147483647]).\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --input-missing-phenotype argument '%s' (must be an integer in [-2147483647, -1] or [3, 2147483647]).\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k_unsafe(flagname_p2, "mport-dosage-certainty")) { @@ -5688,7 +5739,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely((!ScantokDouble(cur_modif, &import_dosage_certainty)) || (import_dosage_certainty < 0.0) || (import_dosage_certainty > 1.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage-certainty parameter '%s' (must be in [0, 1]).\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage-certainty argument '%s' (must be in [0, 1]).\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } import_dosage_certainty *= 1.0 - kSmallEpsilon; @@ -5724,7 +5775,7 @@ goto main_ret_INVALID_CMDLINE; } if (unlikely(ScanUintCappedx(&(cur_modif[6]), kMaxLongLine / 2, &(plink1_dosage_info.skips[skip_idx])))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage skip%u= parameter '%s'.\n", skip_idx, &(cur_modif[6])); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage skip%u= argument '%s'.\n", skip_idx, &(cur_modif[6])); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k(cur_modif, "dose1", cur_modif_slen)) { @@ -5738,13 +5789,13 @@ if (cur_modif_slen == 8) { format_num_m1 = ctou32(cur_modif[7]) - 49; if (unlikely(format_num_m1 >= 3)) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage format= parameter '%c'.\n", cur_modif[7]); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage format= argument '%c'.\n", cur_modif[7]); goto main_ret_INVALID_CMDLINE_2A; } } else if (likely(strequal_k(&(cur_modif[7]), "infer", cur_modif_slen - 7))) { format_num_m1 = 3; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage format= parameter '%s'.\n", &(cur_modif[7])); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage format= argument '%s'.\n", &(cur_modif[7])); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k(cur_modif, "ref-first", cur_modif_slen)) { @@ -5760,7 +5811,7 @@ const char* id_delim_str = &(cur_modif[strlen("id-delim=")]); char cc = ExtractCharParam(id_delim_str); if (unlikely(!cc)) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage id-delim= parameter '%s'.\n", id_delim_str); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage id-delim= argument '%s'.\n", id_delim_str); goto main_ret_INVALID_CMDLINE_WWA; } plink1_dosage_info.id_delim = cc; @@ -5788,7 +5839,7 @@ const char* chr_col_num_start = &(cur_modif[strlen("chr-col-num=")]); uint32_t uii; if (unlikely(ScanPosintCappedx(chr_col_num_start, kMaxLongLine / 2, &uii))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage chr-col-num= parameter '%s'.\n", chr_col_num_start); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage chr-col-num= argument '%s'.\n", chr_col_num_start); goto main_ret_INVALID_CMDLINE_WWA; } plink1_dosage_info.chr_col_idx = uii - 1; @@ -5800,12 +5851,12 @@ const char* pos_col_num_start = &(cur_modif[strlen("pos-col-num=")]); uint32_t uii; if (unlikely(ScanPosintCappedx(pos_col_num_start, kMaxLongLine / 2, &uii))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage pos-col-num= parameter '%s'.\n", pos_col_num_start); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage pos-col-num= argument '%s'.\n", pos_col_num_start); goto main_ret_INVALID_CMDLINE_WWA; } plink1_dosage_info.pos_col_idx = uii - 1; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --import-dosage argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -5929,7 +5980,7 @@ } const char* cur_modif = argvk[arg_idx + param_ct]; if (unlikely((!ScantokDouble(cur_modif, &pc.king_cutoff)) || (pc.king_cutoff < 0.0) || (pc.king_cutoff >= 0.5))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --king-cutoff parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --king-cutoff argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.command_flags1 |= kfCommand1KingCutoff; @@ -5939,7 +5990,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely((!ScantokDouble(cur_modif, &pc.king_table_filter)) || (pc.king_table_filter > 0.5))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --king-table-filter parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --king-table-filter argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k_unsafe(flagname_p2, "ing-table-subset")) { @@ -6034,7 +6085,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(ScanPosintDefcapx(cur_modif, &pc.keep_col_match_num) || (pc.keep_col_match_num == 1))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --keep-col-match-num parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --keep-col-match-num argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else if (likely(strequal_k_unsafe(flagname_p2, "eep-allele-order"))) { @@ -6059,11 +6110,11 @@ } double lambda; if (unlikely(!ScantokDouble(argvk[arg_idx + 1], &lambda))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --lambda parameter '%s'.\n", argvk[arg_idx + 1]); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --lambda argument '%s'.\n", argvk[arg_idx + 1]); goto main_ret_INVALID_CMDLINE_WWA; } if (lambda < 1.0) { - logputs("Note: --lambda parameter set to 1.\n"); + logputs("Note: --lambda argument set to 1.\n"); lambda = 1.0; } pc.adjust_info.lambda = lambda; @@ -6123,7 +6174,7 @@ } else if (likely(!strcmp(cur_modif, "hwe-midp"))) { pc.ld_info.ld_console_flags |= kfLdConsoleHweMidp; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --ld parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --ld argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -6154,16 +6205,16 @@ } const char* mb_modif = argvk[arg_idx + mb_modif_idx]; if (unlikely(ScanPosintptrx(mb_modif, R_CAST(uintptr_t*, &malloc_size_mib)))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --memory parameter '%s'.\n", mb_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --memory argument '%s'.\n", mb_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely(malloc_size_mib < S_CAST(intptr_t, kBigstackMinMib))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --memory parameter '%s' (minimum %u).\n", mb_modif, kBigstackMinMib); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --memory argument '%s' (minimum %u).\n", mb_modif, kBigstackMinMib); goto main_ret_INVALID_CMDLINE_WWA; } #ifndef __LP64__ if (unlikely(malloc_size_mib > S_CAST(intptr_t, kMalloc32bitMibMax))) { - logerrprintf("Error: --memory parameter too large for 32-bit version (max %u).\n", kMalloc32bitMibMax); + logerrprintf("Error: --memory argument too large for 32-bit version (max %u).\n", kMalloc32bitMibMax); goto main_ret_INVALID_CMDLINE; } #endif @@ -6203,7 +6254,7 @@ goto main_ret_INVALID_CMDLINE_WWA; } } else { - char* write_iter = strcpya_k(g_logbuf, "Error: Invalid --make-bed parameter '"); + char* write_iter = strcpya_k(g_logbuf, "Error: Invalid --make-bed argument '"); write_iter = memcpya(write_iter, cur_modif, cur_modif_slen); write_iter = strcpya_k(write_iter, "'."); if ((param_idx == 1) && (!outname_end)) { @@ -6306,7 +6357,7 @@ } else if (likely(strequal_k(cur_modif, "erase-dosage", cur_modif_slen))) { make_plink2_flags |= kfMakePgenEraseDosage; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-bpgen parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-bpgen argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -6447,7 +6498,7 @@ goto main_ret_1; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-pgen parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-pgen argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -6498,7 +6549,7 @@ if (likely(!strcmp(cur_modif, "zs"))) { make_plink2_flags |= kfMakeBimZs; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-just-bim parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-just-bim argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -6546,7 +6597,7 @@ pc.dependency_flags |= kfFilterNonrefFlagsNeededSet; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-just-pvar parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-just-pvar argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -6573,7 +6624,7 @@ goto main_ret_1; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-just-psam parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-just-psam argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else { @@ -6637,7 +6688,7 @@ logerrputs("Error: --make-king 'no-idheader' modifier retired. Use --no-id-header instead.\n"); goto main_ret_INVALID_CMDLINE_A; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-king parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-king argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -6698,7 +6749,7 @@ logerrputs("Error: --make-king-table 'no-idheader' modifier retired. Use --no-id-header\ninstead.\n"); goto main_ret_INVALID_CMDLINE_A; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-king-table parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-king-table argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -6747,7 +6798,7 @@ goto main_ret_1; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --missing parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --missing argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -6786,7 +6837,7 @@ if (likely(!strcmp(cur_modif, "force"))) { pc.misc_flags |= kfMiscMajRefForce; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --maj-ref parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --maj-ref argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -6802,26 +6853,26 @@ if (!mode_str) { pc.min_maf = 0.01; if (unlikely(param_ct == 2)) { - logerrputs("Error: Invalid --maf parameter sequence.\n"); + logerrputs("Error: Invalid --maf argument sequence.\n"); goto main_ret_INVALID_CMDLINE_WWA; } mode_str = cur_modif; } else { if (unlikely(pc.min_maf < 0.0)) { - snprintf(g_logbuf, kLogbufSize, "Error: --maf parameter '%s' too small (must be >= 0).\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: --maf argument '%s' too small (must be >= 0).\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } else if (unlikely(pc.min_maf > 1.0)) { - snprintf(g_logbuf, kLogbufSize, "Error: --maf parameter '%s' too large (must be <= 1).\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: --maf argument '%s' too large (must be <= 1).\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (mode_str[0] == ':') { if (unlikely(param_ct == 2)) { - logerrputs("Error: Invalid --maf parameter sequence.\n"); + logerrputs("Error: Invalid --maf argument sequence.\n"); goto main_ret_INVALID_CMDLINE_WWA; } } else { if (unlikely(mode_str[0])) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --maf parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --maf argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (param_ct == 2) { @@ -6850,21 +6901,21 @@ const char* cur_modif = argvk[arg_idx + 1]; const char* mode_str = ScanadvDouble(cur_modif, &pc.max_maf); if (unlikely(!mode_str)) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-maf parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-maf argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely(pc.max_maf >= 1.0)) { - snprintf(g_logbuf, kLogbufSize, "Error: --max-maf parameter '%s' too large (must be < 1).\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: --max-maf argument '%s' too large (must be < 1).\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (mode_str[0] == ':') { if (unlikely(param_ct == 2)) { - logerrputs("Error: Invalid --max-maf parameter sequence.\n"); + logerrputs("Error: Invalid --max-maf argument sequence.\n"); goto main_ret_INVALID_CMDLINE_WWA; } } else { if (unlikely(mode_str[0])) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-maf parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-maf argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (param_ct == 2) { @@ -6879,7 +6930,7 @@ } } if (unlikely((pc.filter_modes[0] == pc.filter_modes[1]) && (pc.max_maf < pc.min_maf))) { - snprintf(g_logbuf, kLogbufSize, "Error: --max-maf parameter '%s' too small (must be >= %g).\n", cur_modif, pc.min_maf); + snprintf(g_logbuf, kLogbufSize, "Error: --max-maf argument '%s' too small (must be >= %g).\n", cur_modif, pc.min_maf); goto main_ret_INVALID_CMDLINE_WWA; } pc.filter_flags |= kfFilterPvarReq; @@ -6892,7 +6943,7 @@ double dxx; const char* mode_str = ScanadvDouble(cur_modif, &dxx); if (unlikely((!mode_str) || (dxx < 0.0) || (dxx > 2147483646.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mac parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mac argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE; } if (dxx > 0.0) { @@ -6906,12 +6957,12 @@ // yeah, this should be its own function... if (mode_str[0] == ':') { if (unlikely(param_ct == 2)) { - logerrputs("Error: Invalid --mac parameter sequence.\n"); + logerrputs("Error: Invalid --mac argument sequence.\n"); goto main_ret_INVALID_CMDLINE_WWA; } } else { if (unlikely(mode_str[0])) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mac parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mac argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (param_ct == 2) { @@ -6936,19 +6987,19 @@ double dxx; const char* mode_str = ScanadvDouble(cur_modif, &dxx); if (unlikely((!mode_str) || (dxx < 0.0) || (dxx > 2147483646.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-mac parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-mac argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } // round down pc.max_allele_ddosage = S_CAST(int64_t, dxx * kDosageMax); if (mode_str[0] == ':') { if (unlikely(param_ct == 2)) { - logerrputs("Error: Invalid --max-mac parameter sequence.\n"); + logerrputs("Error: Invalid --max-mac argument sequence.\n"); goto main_ret_INVALID_CMDLINE_WWA; } } else { if (unlikely(mode_str[0])) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-mac parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-mac argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (param_ct == 2) { @@ -6964,7 +7015,7 @@ } if (unlikely((pc.filter_modes[2] == pc.filter_modes[3]) && (pc.max_allele_ddosage < pc.min_allele_ddosage))) { // yeah, --mac 0.1 --max-mac 0.1 also isn't allowed - logerrputs("Error: --max-mac parameter cannot be smaller than --mac parameter when modes\nare identical.\n"); + logerrputs("Error: --max-mac argument cannot be smaller than --mac argument when modes are\nidentical.\n"); goto main_ret_INVALID_CMDLINE; } pc.filter_flags |= kfFilterPvarReq; @@ -6981,13 +7032,13 @@ } else if (!strcmp(cur_modif, "hh-missing")) { pc.misc_flags |= kfMiscMindHhMissing; } else if (unlikely(mind_thresh_present)) { - logerrputs("Error: Invalid --mind parameter sequence.\n"); + logerrputs("Error: Invalid --mind argument sequence.\n"); goto main_ret_INVALID_CMDLINE_A; } else if (unlikely(!ScantokDouble(cur_modif, &pc.mind_thresh))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mind parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mind argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } else if (unlikely((pc.mind_thresh < 0.0) || (pc.mind_thresh > 1.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mind parameter '%s' (must be in [0, 1]).\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mind argument '%s' (must be in [0, 1]).\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } else { mind_thresh_present = 1; @@ -7039,11 +7090,11 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(!ScantokDouble(cur_modif, &pc.glm_info.max_corr))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-corr parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-corr argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely((pc.glm_info.max_corr < 0.0) || (pc.glm_info.max_corr > 1.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-corr parameter '%s' (must be in [0, 1]).\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-corr argument '%s' (must be in [0, 1]).\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k_unsafe(flagname_p2, "ach-r2-filter")) { @@ -7057,24 +7108,24 @@ if (param_ct) { const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(!ScantokDouble(cur_modif, &pc.mach_r2_min))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mach-r2-filter min parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mach-r2-filter min argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely(pc.mach_r2_min < 0.0)) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mach-r2-filter min parameter '%s' (must be nonnegative).\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mach-r2-filter min argument '%s' (must be nonnegative).\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (param_ct == 2) { cur_modif = argvk[arg_idx + 2]; if (unlikely((!ScantokDouble(cur_modif, &pc.mach_r2_max)) || (pc.mach_r2_max == 0.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mach-r2-filter max parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mach-r2-filter max argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else { pc.mach_r2_max = 2.0; } if (unlikely(pc.mach_r2_max < pc.mach_r2_min)) { - logerrputs("Error: --mach-r2-filter min parameter cannot be larger than max parameter.\n"); + logerrputs("Error: --mach-r2-filter min argument cannot be larger than max argument.\n"); goto main_ret_INVALID_CMDLINE_A; } } else { @@ -7096,33 +7147,33 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(!ScantokDouble(cur_modif, &pc.minimac3_r2_min))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --minimac3-r2-filter min parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --minimac3-r2-filter min argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely(pc.minimac3_r2_min < 0.0)) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --minimac3-r2-filter min parameter '%s' (must be nonnegative).\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --minimac3-r2-filter min argument '%s' (must be nonnegative).\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (param_ct == 2) { cur_modif = argvk[arg_idx + 2]; if (unlikely((!ScantokDouble(cur_modif, &pc.minimac3_r2_max)) || (pc.minimac3_r2_max == 0.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --minimac3-r2-filter max parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --minimac3-r2-filter max argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else { pc.minimac3_r2_max = 1.0; } if (unlikely(pc.minimac3_r2_max < pc.minimac3_r2_min)) { - logerrputs("Error: --minimac3-r2-filter min parameter cannot be larger than max parameter.\n"); + logerrputs("Error: --minimac3-r2-filter min argument cannot be larger than max argument.\n"); goto main_ret_INVALID_CMDLINE_A; } pc.filter_flags |= kfFilterPvarReq; pc.dependency_flags |= kfFilterAllReq | kfFilterNoSplitChr; } else if (strequal_k_unsafe(flagname_p2, "issing-code")) { - if (unlikely(!(xload & (kfXloadOxGen | kfXloadOxBgen)))) { + if (unlikely(!(xload & (kfXloadOxGen | kfXloadOxBgen | kfXloadOxHaps)))) { // could technically support pure .sample -> .fam/.psam, but let's // keep this simple - logerrputs("Error: --missing-code must be used with --data/--gen/--bgen.\n"); + logerrputs("Error: --missing-code must be used with --data/--gen/--bgen/--haps.\n"); goto main_ret_INVALID_CMDLINE_A; } if (unlikely(EnforceParamCtRange(argvk[arg_idx], param_ct, 0, 1))) { @@ -7190,7 +7241,7 @@ } else if (likely(strequal_k(cur_modif, "iid-only", cur_modif_slen))) { pc.grm_flags |= kfGrmNoIdHeaderIidOnly; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-grm-bin parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-grm-bin argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -7241,7 +7292,7 @@ } else if (likely(strequal_k(cur_modif, "iid-only", cur_modif_slen))) { pc.grm_flags |= kfGrmNoIdHeaderIidOnly; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-grm-list parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-grm-list argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -7317,7 +7368,7 @@ } pc.grm_flags |= kfGrmMatrixTri; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-rel parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --make-rel argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -7351,7 +7402,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(ScanPosintCappedx(cur_modif, kMaxLongLine / 2, &pc.mwithin_val))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mwithin parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mwithin argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k_unsafe(flagname_p2, "filter")) { @@ -7374,7 +7425,7 @@ const char* cur_modif = argvk[arg_idx + 1]; uint32_t mfilter_arg; if (unlikely(ScanPosintDefcapx(cur_modif, &mfilter_arg))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mfilter parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mfilter argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.keep_col_match_num = mfilter_arg + 2; @@ -7384,7 +7435,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(ScanPosintDefcapx(cur_modif, &pc.filter_max_allele_ct))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-alleles parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --max-alleles argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.filter_flags |= kfFilterPvarReq; @@ -7394,7 +7445,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(ScanPosintDefcapx(cur_modif, &pc.filter_min_allele_ct))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --min-alleles parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --min-alleles argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely(pc.filter_min_allele_ct > pc.filter_max_allele_ct)) { @@ -7413,7 +7464,7 @@ const char* cur_modif = argvk[arg_idx + 1]; uint32_t mpheno_arg; if (unlikely(ScanPosintDefcapx(cur_modif, &mpheno_arg))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mpheno parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --mpheno argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } // add two to the number, convert it back to a string, and act as if @@ -7486,7 +7537,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(ScanPosintCappedx(cur_modif, kMaxIdSlen - 2, &pc.new_variant_id_max_allele_slen))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --new-id-max-allele-len length parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --new-id-max-allele-len length argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (param_ct == 2) { @@ -7497,7 +7548,7 @@ } else if (strequal_k(cur_modif, "truncate", cur_modif_slen)) { pc.misc_flags |= kfMiscNewVarIdOverflowTruncate; } else if (unlikely(!strequal_k(cur_modif, "error", cur_modif_slen))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --new-id-max-allele-len parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --new-id-max-allele-len argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -7517,7 +7568,7 @@ if (param_ct) { const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(strcmp(cur_modif, "list"))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --normalize parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --normalize argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.fa_flags |= kfFaNormalizeList; @@ -7538,7 +7589,7 @@ if (param_ct) { const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(strcmp(cur_modif, "iid-only"))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --no-id-header parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --no-id-header argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.misc_flags |= kfMiscNoIdHeaderIidOnly; @@ -7571,7 +7622,7 @@ } else if (likely(strequal_k(mt_code, "26", code_slen))) { chr_info.output_encoding = kfChrOutput0; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --output-chr parameter '%s'.\n", mt_code); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --output-chr argument '%s'.\n", mt_code); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k_unsafe(flagname_p2, "utput-min-p")) { @@ -7580,7 +7631,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely((!ScantokLn(cur_modif, &pc.output_min_ln)) || (pc.output_min_ln >= 0.0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --output-min-p parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --output-min-p argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k_unsafe(flagname_p2, "xford-single-chr")) { @@ -7613,7 +7664,7 @@ const char* cur_modif = argvk[arg_idx + 1]; output_missing_geno_char = ExtractCharParam(cur_modif); if (unlikely(ctou32(output_missing_geno_char) <= ' ')) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --output-missing-genotype parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --output-missing-genotype argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else if (strequal_k_unsafe(flagname_p2, "utput-missing-phenotype")) { @@ -7651,7 +7702,7 @@ const char* fname_prefix = argvk[arg_idx + fname_modif_idx]; const uint32_t slen = strlen(fname_prefix); if (unlikely(slen > (kPglFnamesize - 11))) { - logerrputs("Error: --pfile parameter too long.\n"); + logerrputs("Error: --pfile argument too long.\n"); goto main_ret_OPEN_FAIL; } snprintf(memcpya(pgenname, fname_prefix, slen), 10, ".pgen"); @@ -7673,7 +7724,7 @@ const char* fname = argvk[arg_idx + 1]; const uint32_t slen = strlen(fname); if (unlikely(slen > (kPglFnamesize - 2))) { - logerrputs("Error: --pgen parameter too long.\n"); + logerrputs("Error: --pgen argument too long.\n"); goto main_ret_OPEN_FAIL; } memcpy(pgenname, fname, slen + 1); @@ -7688,7 +7739,7 @@ const char* fname = argvk[arg_idx + 1]; const uint32_t slen = strlen(fname); if (unlikely(slen > (kPglFnamesize - 2))) { - logerrputs("Error: --psam parameter too long.\n"); + logerrputs("Error: --psam argument too long.\n"); goto main_ret_OPEN_FAIL; } memcpy(psamname, fname, slen + 1); @@ -7703,7 +7754,7 @@ const char* fname = argvk[arg_idx + 1]; const uint32_t slen = strlen(fname); if (unlikely(slen > (kPglFnamesize - 2))) { - logerrputs("Error: --pvar parameter too long.\n"); + logerrputs("Error: --pvar argument too long.\n"); goto main_ret_OPEN_FAIL; } memcpy(pvarname, fname, slen + 1); @@ -7787,7 +7838,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(!ScantokLn(cur_modif, &pc.ln_pfilter))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --pfilter parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --pfilter argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely((pc.ln_pfilter == -DBL_MAX) || (pc.ln_pfilter > 0.0))) { @@ -7817,7 +7868,7 @@ pc.pca_flags |= kfPcaBiallelicVarWts; } else if (strequal_k(cur_modif, "vzs", cur_modif_slen)) { pc.pca_flags |= kfPcaVarZs; - } else if (StrStartsWith(cur_modif, "scols=", cur_modif_slen)) { + } else if (StrStartsWith0(cur_modif, "scols=", cur_modif_slen)) { if (unlikely(explicit_scols)) { logerrputs("Error: Multiple --pca scols= modifiers.\n"); goto main_ret_INVALID_CMDLINE; @@ -7827,7 +7878,7 @@ goto main_ret_1; } explicit_scols = 1; - } else if (StrStartsWith(cur_modif, "vcols=", cur_modif_slen)) { + } else if (StrStartsWith0(cur_modif, "vcols=", cur_modif_slen)) { if (unlikely(vcols_idx)) { logerrputs("Error: Multiple --pca vcols= modifiers.\n"); goto main_ret_INVALID_CMDLINE; @@ -7841,7 +7892,7 @@ goto main_ret_INVALID_CMDLINE_A; } else { if (unlikely(pc.pca_ct || ScanPosintDefcapx(cur_modif, &pc.pca_ct))) { - logerrputs("Error: Invalid --pca parameter sequence.\n"); + logerrputs("Error: Invalid --pca argument sequence.\n"); goto main_ret_INVALID_CMDLINE_A; } if (unlikely(pc.pca_ct > 8000)) { @@ -7984,11 +8035,11 @@ pc.score_info.flags |= kfScoreQsrMin; } else { if (unlikely(ScanPosintCappedx(cur_modif, kMaxLongLine / 2, &(qsr_cols[numeric_param_ct])))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --q-score-range parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --q-score-range argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely(numeric_param_ct == 2)) { - logerrputs("Error: --q-score-range takes at most two numeric parameters.\n"); + logerrputs("Error: --q-score-range takes at most two numeric arguments.\n"); goto main_ret_INVALID_CMDLINE_A; } if (unlikely(numeric_param_ct && (qsr_cols[0] == qsr_cols[1]))) { @@ -8048,6 +8099,12 @@ pc.filter_flags |= kfFilterPsamReq | kfFilterExclNosex; goto main_param_zero; } else if (strequal_k_unsafe(flagname_p2, "ead-freq")) { + if (unlikely(pc.command_flags1 & kfCommand1AlleleFreq)) { + // --read-freq can't promise OBS_CTs, so simplest to just continue + // disallowing this + logerrputs("Error: --freq and --read-freq cannot be used together.\n"); + goto main_ret_INVALID_CMDLINE_A; + } if (unlikely(EnforceParamCtRange(argvk[arg_idx], param_ct, 1, 1))) { goto main_ret_INVALID_CMDLINE_2A; } @@ -8140,7 +8197,7 @@ if (!strcmp(sources[0], "force")) { --param_ct; if (unlikely(!param_ct)) { - logerrputs("Error: Invalid --ref-allele parameter sequence.\n"); + logerrputs("Error: Invalid --ref-allele argument sequence.\n"); goto main_ret_INVALID_CMDLINE_A; } pc.misc_flags |= kfMiscRefAlleleForce; @@ -8169,7 +8226,7 @@ } else if (likely((!fname_modif_idx) && (!pc.fa_fname))) { fname_modif_idx = param_idx; } else { - logerrputs("Error: Invalid --ref-from-fa parameter sequence.\n"); + logerrputs("Error: Invalid --ref-from-fa argument sequence.\n"); goto main_ret_INVALID_CMDLINE_A; } } @@ -8178,7 +8235,7 @@ if (unlikely(reterr)) { goto main_ret_1; } - logerrputs("Warning: Filename-parameter form of --ref-from-fa is deprecated. Use --fa to\nspecify the .fa file instead.\n"); + logerrputs("Warning: Filename-argument form of --ref-from-fa is deprecated. Use --fa to\nspecify the .fa file instead.\n"); } else if (unlikely(!pc.fa_fname)) { logerrputs("Error: --ref-from-fa requires --fa.\n"); goto main_ret_INVALID_CMDLINE_A; @@ -8196,7 +8253,7 @@ if (strequal_k(cur_modif, "list", cur_modif_slen)) { pc.command_flags1 |= kfCommand1RmDupList; } else if (rmdup_mode != kRmDup0) { - logerrputs("Error: Invalid --rm-dup parameter sequence.\n"); + logerrputs("Error: Invalid --rm-dup argument sequence.\n"); goto main_ret_INVALID_CMDLINE_A; } else if (strequal_k(cur_modif, "error", cur_modif_slen)) { rmdup_mode = kRmDupError; @@ -8209,7 +8266,7 @@ } else if (likely(strequal_k(cur_modif, "force-first", cur_modif_slen))) { rmdup_mode = kRmDupForceFirst; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --rm-dup parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --rm-dup argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -8239,7 +8296,7 @@ pc.recover_var_ids_flags |= kfRecoverVarIdsPartial; } else { if (unlikely(fname_idx)) { - logerrputs("Error: Invalid --recover-var-ids parameter sequence.\n"); + logerrputs("Error: Invalid --recover-var-ids argument sequence.\n"); goto main_ret_INVALID_CMDLINE_A; } fname_idx = param_idx; @@ -8293,7 +8350,7 @@ for (uint32_t param_idx = 1; param_idx <= param_ct; ++param_idx) { const char* cur_modif = argvk[arg_idx + param_idx]; if (unlikely(ScanUintCappedx(cur_modif, UINT32_MAX, &(rseeds[param_idx - 1])))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --seed parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --seed argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -8326,11 +8383,11 @@ } } else { if (unlikely(ScanUintDefcapx(argvk[arg_idx + 1], &pc.splitpar_bound1))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --split-par parameter '%s'.\n", argvk[arg_idx + 1]); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --split-par argument '%s'.\n", argvk[arg_idx + 1]); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely(ScanUintDefcapx(argvk[arg_idx + 2], &pc.splitpar_bound2) || (pc.splitpar_bound2 <= pc.splitpar_bound1))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --split-par parameter '%s'.\n", argvk[arg_idx + 2]); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --split-par argument '%s'.\n", argvk[arg_idx + 2]); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -8371,7 +8428,7 @@ } make_plink2_flags |= kfMakePlink2SetHhMissingKeepDosage; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --set-hh-missing parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --set-hh-missing argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -8393,7 +8450,7 @@ } make_plink2_flags |= kfMakePlink2SetMixedMtMissingKeepDosage; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --set-mixed-mt-missing parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --set-mixed-mt-missing argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -8461,7 +8518,7 @@ if (likely(!strcmp(cur_modif, "just-acgt"))) { pc.filter_flags |= kfFilterSnpsOnlyJustAcgt; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --snps-only parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --snps-only argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -8517,11 +8574,11 @@ } } else { if (unlikely(ScanPosintCappedx(cur_modif, kMaxLongLine / 2, &(score_cols[numeric_param_ct])))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --score parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --score argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (unlikely(numeric_param_ct == 3)) { - logerrputs("Error: --score takes at most three numeric parameters.\n"); + logerrputs("Error: --score takes at most three numeric arguments.\n"); goto main_ret_INVALID_CMDLINE_A; } for (uint32_t uii = 0; uii != numeric_param_ct; ++uii) { @@ -8576,7 +8633,7 @@ goto main_ret_INVALID_CMDLINE_A; } if (unlikely(pc.score_info.input_col_idx_range_list.name_ct)) { - logerrputs("Error: --score-col-nums cannot be used when three numeric parameters are\nprovided to --score.\n"); + logerrputs("Error: --score-col-nums cannot be used when three numeric arguments are\nprovided to --score.\n"); goto main_ret_INVALID_CMDLINE_A; } reterr = ParseNameRanges(&(argvk[arg_idx]), errstr_append, param_ct, 1, '-', &pc.score_info.input_col_idx_range_list); @@ -8686,12 +8743,12 @@ if (cur_modif[6] == '\0') { pc.sdiff_info.dosage_hap_tol = 0; } else if (cur_modif[6] != '=') { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --sample-diff parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --sample-diff argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } else { double dxx; if (unlikely((!ScantokDouble(&(cur_modif[7]), &dxx)) || (dxx < 0.0) || (dxx > (0.5 - kSmallEpsilon)))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --sample-diff parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --sample-diff argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.sdiff_info.dosage_hap_tol = S_CAST(int32_t, dxx * ((1 + kSmallEpsilon) * kDosageMax)); @@ -8754,12 +8811,12 @@ is_file = 1; break; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --sample-diff parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --sample-diff argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } if (param_idx > param_ct) { - logerrputs("Error: Invalid --sample-diff parameter sequence (base=/id= must be\nsecond-to-last parameter or earlier, or file= must be last parameter).\n"); + logerrputs("Error: Invalid --sample-diff argument sequence (base=/id= must be\nsecond-to-last argument or earlier, or file= must be last argument).\n"); goto main_ret_INVALID_CMDLINE_A; } if (diff_cols_param_idx) { @@ -8848,7 +8905,7 @@ goto main_ret_1; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --sample-counts parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --sample-counts argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -8871,11 +8928,11 @@ goto main_ret_INVALID_CMDLINE_2A; } if (unlikely(ScanPosintDefcapx(argvk[arg_idx + 1], &pc.max_thread_ct))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --threads parameter '%s'.\n", argvk[arg_idx + 1]); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --threads argument '%s'.\n", argvk[arg_idx + 1]); goto main_ret_INVALID_CMDLINE_WWA; } if (pc.max_thread_ct > kMaxThreads) { - logprintf("Note: Reducing --threads parameter to %u. (If this is not large enough,\nrecompile with a larger kMaxThreads setting.)\n", kMaxThreads); + logprintfww("Note: Reducing --threads argument to %u. (If this is not large enough,\nrecompile with a larger kMaxThreads setting.)\n", kMaxThreads); pc.max_thread_ct = kMaxThreads; } else if (known_procs == -1) { // trigger BLAS/LAPACK warning? @@ -8913,7 +8970,7 @@ const char* cur_modif = argvk[arg_idx + 1]; double dxx; if (unlikely(!ScantokDouble(cur_modif, &dxx))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --to-bp/-kb/-mb parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --to-bp/-kb/-mb argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } const char unit_char = flagname_p2[2]; @@ -8923,7 +8980,7 @@ dxx *= 1000000; } if (unlikely(dxx < 0)) { - logerrprintf("Error: --to-bp/-kb/-mb parameter '%s' too small.\n", cur_modif); + logerrprintf("Error: --to-bp/-kb/-mb argument '%s' too small.\n", cur_modif); goto main_ret_INVALID_CMDLINE_A; } else if (dxx >= 2147483646) { pc.to_bp = 0x7ffffffe; @@ -8933,7 +8990,7 @@ } if (unlikely(pc.from_bp > pc.to_bp)) { // (if we do permit this, rounding must be postponed) - logerrputs("Error: --to-bp/-kb/-mb parameter is smaller than --from-bp/-kb/-mb parameter.\n"); + logerrputs("Error: --to-bp/-kb/-mb argument is smaller than --from-bp/-kb/-mb argument.\n"); goto main_ret_INVALID_CMDLINE; } pc.filter_flags |= kfFilterPvarReq; @@ -8970,7 +9027,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(ScanUintDefcapx(cur_modif, &pc.thin_keep_ct) || (!pc.thin_keep_ct))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --thin-count parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --thin-count argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.filter_flags |= kfFilterPvarReq; @@ -9001,7 +9058,7 @@ } const char* cur_modif = argvk[arg_idx + 1]; if (unlikely(ScanUintDefcapx(cur_modif, &pc.thin_keep_sample_ct) || (!pc.thin_keep_sample_ct))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --thin-indiv-count parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --thin-indiv-count argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } pc.filter_flags |= kfFilterPsamReq; @@ -9044,20 +9101,20 @@ } else if (StrStartsWith(cur_modif, "col-num=", cur_modif_slen)) { const char* col_num_start = &(cur_modif[strlen("col-num=")]); if (unlikely(ScanPosintDefcapx(col_num_start, &pc.update_sex_info.col_num) || (pc.update_sex_info.col_num == 1))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --update-sex col-num= parameter '%s'.\n", col_num_start); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --update-sex col-num= argument '%s'.\n", col_num_start); goto main_ret_INVALID_CMDLINE_WWA; } } else if (likely(param_ct == 2)) { - // only one extra parameter, try to interpret it the plink 1.9 - // way but print a warning + // only one extra argument, try to interpret it the plink 1.9 way + // but print a warning if (unlikely(ScanPosintDefcapx(cur_modif, &pc.update_sex_info.col_num))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --update-sex parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --update-sex argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } - logerrputs("Warning: --update-sex unlabeled column parameter is now deprecated. Use\n'col-num=' instead (and add 2 to the value).\n"); + logerrputs("Warning: --update-sex unlabeled column argument is now deprecated. Use\n'col-num=' instead (and add 2 to the value).\n"); pc.update_sex_info.col_num += 2; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --update-sex parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --update-sex argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -9139,7 +9196,7 @@ goto main_ret_INVALID_CMDLINE_2A; } if (unlikely(ScanFloat(argvk[arg_idx + 1], &pc.var_min_qual) || (pc.var_min_qual < S_CAST(float, 0.0)))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --var-min-qual parameter '%s'.\n", argvk[arg_idx + 1]); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --var-min-qual argument '%s'.\n", argvk[arg_idx + 1]); goto main_ret_INVALID_CMDLINE_WWA; } pc.var_min_qual *= S_CAST(float, 1 - kSmallEpsilon); @@ -9168,7 +9225,7 @@ // tolerate vcf-dosage= as well, so it's possible to use the same // pattern as --export if (unlikely((!StrStartsWith(cur_modif, "dosage=", cur_modif_slen)) && (!StrStartsWith(cur_modif, "vcf-dosage=", cur_modif_slen)))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --vcf parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --vcf argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } const char* dosage_field_start = (cur_modif[0] == 'v')? &(cur_modif[11]) : &(cur_modif[7]); @@ -9177,11 +9234,11 @@ goto main_ret_1; } if (unlikely(!((!strcmp(vcf_dosage_import_field, "GP-force")) || IsAlphanumeric(vcf_dosage_import_field)))) { - logerrputs("Error: --vcf dosage= parameter is not alphanumeric.\n"); + logerrputs("Error: --vcf dosage= argument is not alphanumeric.\n"); goto main_ret_INVALID_CMDLINE; } if (unlikely(!strcmp(vcf_dosage_import_field, "GT"))) { - logerrputs("Error: --vcf dosage= parameter cannot be 'GT'.\n"); + logerrputs("Error: --vcf dosage= argument cannot be 'GT'.\n"); goto main_ret_INVALID_CMDLINE; } } @@ -9197,9 +9254,8 @@ logerrputs("Error: --vcf-min-gp is no longer supported. Use --import-dosage-certainty\ninstead.\n"); goto main_ret_INVALID_CMDLINE_A; } else if (strequal_k_unsafe(flagname_p2, "cf-min-gq") || strequal_k_unsafe(flagname_p2, "cf-min-dp")) { - if (unlikely(!(xload & kfXloadVcf))) { - // todo: support BCF too - logerrprintf("Error: --%s must be used with --vcf.\n", flagname_p); + if (unlikely(!(xload & (kfXloadVcf | kfXloadBcf)))) { + logerrprintf("Error: --%s must be used with --vcf/--bcf.\n", flagname_p); goto main_ret_INVALID_CMDLINE; } if (unlikely(EnforceParamCtRange(argvk[arg_idx], param_ct, 1, 1))) { @@ -9208,7 +9264,7 @@ const char* cur_modif = argvk[arg_idx + 1]; uint32_t uii; if (unlikely(ScanUintDefcapx(cur_modif, &uii))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --%s parameter '%s'.\n", flagname_p, cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --%s argument '%s'.\n", flagname_p, cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } if (flagname_p2[7] == 'g') { @@ -9221,7 +9277,7 @@ } } } else if (strequal_k_unsafe(flagname_p2, "cf-max-dp")) { - if (unlikely(!(xload & kfXloadVcf))) { + if (unlikely(!(xload & (kfXloadVcf | kfXloadBcf)))) { logerrputs("Error: --vcf-max-dp must be used with --vcf/--bcf.\n"); goto main_ret_INVALID_CMDLINE; } @@ -9231,7 +9287,7 @@ const char* cur_modif = argvk[arg_idx + 1]; uint32_t uii; if (unlikely(ScanUintDefcapx(cur_modif, &uii))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --vcf-max-dp parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --vcf-max-dp argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } vcf_max_dp = uii; @@ -9249,16 +9305,16 @@ } idspace_to = ExtractCharParam(argvk[arg_idx + 1]); if (unlikely(!idspace_to)) { - logerrputs("Error: --vcf-idspace-to parameter must be a single character.\n"); + logerrputs("Error: --vcf-idspace-to argument must be a single character.\n"); goto main_ret_INVALID_CMDLINE_A; } if (unlikely(ctou32(idspace_to) <= ' ')) { - logerrputs("Error: --vcf-idspace-to parameter must be a nonspace character.\n"); + logerrputs("Error: --vcf-idspace-to argument must be a nonspace character.\n"); goto main_ret_INVALID_CMDLINE; } } else if (strequal_k_unsafe(flagname_p2, "cf-half-call")) { - if (unlikely(!(xload & kfXloadVcf))) { - logerrputs("Error: --vcf-half-call must be used with --vcf.\n"); + if (unlikely(!(xload & (kfXloadVcf | kfXloadBcf)))) { + logerrputs("Error: --vcf-half-call must be used with --vcf/--bcf.\n"); goto main_ret_INVALID_CMDLINE; } if (unlikely(EnforceParamCtRange(argvk[arg_idx], param_ct, 1, 1))) { @@ -9279,7 +9335,7 @@ } else if (likely( ((mode_slen == 1) && (first_char_upcase_match == 'R')) || strequal_k(mode_str, "reference", mode_slen))) { - vcf_half_call = kVcfHalfCallError; + vcf_half_call = kVcfHalfCallReference; } else { snprintf(g_logbuf, kLogbufSize, "Error: '%s' is not a valid mode for --vcf-half-call.\n", mode_str); goto main_ret_INVALID_CMDLINE_WWA; @@ -9356,7 +9412,7 @@ // (.vscore.vars) file accompanying the .vscore.bin can be large // enough to deserve compression. pc.vscore_flags |= kfVscoreBin; - } else if (likely(StrStartsWith(cur_modif, "cols=", cur_modif_slen))) { + } else if (likely(StrStartsWith0(cur_modif, "cols=", cur_modif_slen))) { if (unlikely(explicit_cols)) { logerrputs("Error: Multiple --variant-score cols= modifiers.\n"); goto main_ret_INVALID_CMDLINE; @@ -9367,7 +9423,7 @@ goto main_ret_1; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --variant-score parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --variant-score argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -9410,7 +9466,7 @@ } else if (likely(strequal_k(cur_modif, "allow-dups", cur_modif_slen))) { pc.misc_flags |= kfMiscWriteSnplistAllowDups; } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --write-snplist parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --write-snplist argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } @@ -9435,7 +9491,7 @@ const char* cur_modif = argvk[arg_idx + 1]; double dxx; if (unlikely((!ScantokDouble(cur_modif, &dxx)) || (dxx < 0))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --window parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --window argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } dxx *= 500 * (1 + kSmallEpsilon); @@ -9488,7 +9544,7 @@ goto main_ret_1; } } else { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --write-covar parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --write-covar argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else { @@ -9526,7 +9582,7 @@ const char* cur_modif = argvk[arg_idx + 1]; pc.xchr_model = ctou32(ExtractCharParam(cur_modif)) - 48; if (unlikely(pc.xchr_model > 2)) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --xchr-model parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --xchr-model argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else { @@ -9546,7 +9602,7 @@ // I've postponed the decision on whether this sort of nondeterminism // is acceptable in plink2 for now (by reverting to 1.3.3). if (unlikely(ScanPosintCappedx(cur_modif, 22, &g_zst_level))) { - snprintf(g_logbuf, kLogbufSize, "Error: Invalid --zst-level parameter '%s'.\n", cur_modif); + snprintf(g_logbuf, kLogbufSize, "Error: Invalid --zst-level argument '%s'.\n", cur_modif); goto main_ret_INVALID_CMDLINE_WWA; } } else { @@ -9558,8 +9614,8 @@ goto main_ret_INVALID_CMDLINE_UNRECOGNIZED; main_param_zero: if (unlikely(param_ct)) { - snprintf(g_logbuf, kLogbufSize, "Error: --%s doesn't accept parameters.\n", flagname_p); - goto main_ret_INVALID_CMDLINE_2A; + snprintf(g_logbuf, kLogbufSize, "Error: You provided %u argument%s to --%s, which has no parameters.\n", param_ct, (param_ct == 1)? "" : "s", flagname_p); + goto main_ret_INVALID_CMDLINE_WWA; } } } while ((++cur_flag_idx) < flag_ct); @@ -9779,18 +9835,18 @@ goto main_ret_INVALID_CMDLINE_A; } if (unlikely((output_missing_geno_char != '.') && (output_missing_geno_char != input_missing_geno_char))) { - logerrputs("Error: --output-missing-genotype and --input-missing-genotype parameters cannot\nbe inconsistent when --keep-autoconv is specified.\n"); + logerrputs("Error: --output-missing-genotype and --input-missing-genotype arguments cannot\nbe inconsistent when --keep-autoconv is specified.\n"); goto main_ret_INVALID_CMDLINE_A; } double dxx; const char* num_end = ScantokDouble(g_output_missing_pheno, &dxx); if (num_end) { if (unlikely(dxx != S_CAST(double, pc.missing_pheno))) { - logerrputs("Error: --output-missing-phenotype and --input-missing-phenotype parameters\ncannot be inconsistent when --keep-autoconv is specified.\n"); + logerrputs("Error: --output-missing-phenotype and --input-missing-phenotype arguments\ncannot be inconsistent when --keep-autoconv is specified.\n"); goto main_ret_INVALID_CMDLINE_A; } } else if (unlikely(!IsNanStr(g_output_missing_pheno, strlen(g_output_missing_pheno)))) { - logerrputs("Error: --output-missing-phenotype parameter must be numeric or 'NA' when\n--keep-autoconv is specified.\n"); + logerrputs("Error: --output-missing-phenotype argument must be numeric or 'NA' when\n--keep-autoconv is specified.\n"); goto main_ret_INVALID_CMDLINE_A; } } else { @@ -9802,19 +9858,19 @@ const uint32_t convname_slen = convname_end - outname; uint32_t pgen_generated = 1; uint32_t psam_generated = 1; - if (xload & kfXloadVcf) { + if (xload & (kfXloadVcf | kfXloadBcf)) { const uint32_t no_samples_ok = !(pc.dependency_flags & (kfFilterAllReq | kfFilterPsamReq)); - if (no_samples_ok && (!(import_flags & kfImportKeepAutoconv)) && pc.command_flags1) { + const uint32_t is_vcf = (xload / kfXloadVcf) & 1; + if (no_samples_ok && is_vcf && (!(import_flags & kfImportKeepAutoconv)) && pc.command_flags1) { // special case: just treat the VCF as a .pvar file strcpy(pvarname, pgenname); pgenname[0] = '\0'; goto main_reinterpret_vcf_instead_of_converting; - } else { + } else if (is_vcf) { reterr = VcfToPgen(pgenname, (load_params & kfLoadParamsPsam)? psamname : nullptr, const_fid, vcf_dosage_import_field, pc.misc_flags, import_flags, no_samples_ok, pc.hard_call_thresh, pc.dosage_erase_thresh, import_dosage_certainty, id_delim, idspace_to, vcf_min_gq, vcf_min_dp, vcf_max_dp, vcf_half_call, pc.fam_cols, pc.max_thread_ct, outname, convname_end, &chr_info, &pgen_generated, &psam_generated); + } else { + reterr = BcfToPgen(pgenname, (load_params & kfLoadParamsPsam)? psamname : nullptr, const_fid, vcf_dosage_import_field, pc.misc_flags, import_flags, no_samples_ok, pc.hard_call_thresh, pc.dosage_erase_thresh, import_dosage_certainty, id_delim, idspace_to, vcf_min_gq, vcf_min_dp, vcf_max_dp, vcf_half_call, pc.fam_cols, pc.max_thread_ct, outname, convname_end, &chr_info, &pgen_generated, &psam_generated); } - } else if (xload & kfXloadBcf) { - logerrputs("Error: --bcf is not implemented yet.\n"); - reterr = kPglRetNotYetSupported; } else if (xload & kfXloadOxGen) { reterr = OxGenToPgen(pgenname, psamname, import_single_chr_str, ox_missing_code, pc.misc_flags, import_flags, oxford_import_flags, pc.hard_call_thresh, pc.dosage_erase_thresh, import_dosage_certainty, pc.max_thread_ct, outname, convname_end, &chr_info); } else if (xload & kfXloadOxBgen) { @@ -9939,7 +9995,14 @@ break; } main_ret_1: - DispExitMsg(reterr); + if (reterr == kPglRetNomemCustomMsg) { + if (g_failed_alloc_attempt_size) { + logerrprintf("Failed allocation size: %" PRIuPTR "\n", g_failed_alloc_attempt_size); + } + reterr = kPglRetNomem; + } else { + DispExitMsg(reterr); + } while (0) { main_ret_NOMEM_NOLOG: PrintVer(); diff -Nru plink2-2.00~a3-200116+dfsg/plink2_cmdline.cc plink2-2.00~a3-200217+dfsg/plink2_cmdline.cc --- plink2-2.00~a3-200116+dfsg/plink2_cmdline.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_cmdline.cc 2020-02-09 04:36:07.000000000 +0000 @@ -22,8 +22,11 @@ # include #endif +#include // open() +#include // open() +#include // open() #include // time(), ctime() -#include // getcwd(), gethostname(), sysconf() +#include // getcwd(), gethostname(), sysconf(), fstat() #ifdef __cplusplus namespace plink2 { @@ -107,6 +110,24 @@ g_stderr_written_to = 1; } +PglErr ForceNonFifo(const char* fname) { + int32_t file_handle = open(fname, O_RDONLY); + if (unlikely(file_handle < 0)) { + return kPglRetOpenFail; + } + struct stat statbuf; + if (unlikely(fstat(file_handle, &statbuf) < 0)) { + close(file_handle); + return kPglRetOpenFail; + } + if (unlikely(S_ISFIFO(statbuf.st_mode))) { + close(file_handle); + return kPglRetRewindFail; + } + close(file_handle); + return kPglRetSuccess; +} + BoolErr fopen_checked(const char* fname, const char* mode, FILE** target_ptr) { /* if (!strcmp(mode, FOPEN_WB)) { @@ -398,9 +419,8 @@ } uint32_t GetParamCt(const char* const* argvk, uint32_t argc, uint32_t flag_idx) { - // Counts the number of optional parameters given to the flag at position - // flag_idx, treating any nonnumeric parameter beginning with "-" as - // optional. + // Counts the number of optional arguments given to the flag at position + // flag_idx, treating any nonnumeric argument beginning with "-" as optional. ++flag_idx; uint32_t cur_idx = flag_idx; while ((cur_idx < argc) && (!IsCmdlineFlag(argvk[cur_idx]))) { @@ -412,9 +432,9 @@ BoolErr EnforceParamCtRange(const char* flag_name, uint32_t param_ct, uint32_t min_ct, uint32_t max_ct) { if (unlikely(param_ct > max_ct)) { if (max_ct > min_ct) { - snprintf(g_logbuf, kLogbufSize, "Error: %s accepts at most %u parameter%s.\n", flag_name, max_ct, (max_ct == 1)? "" : "s"); + snprintf(g_logbuf, kLogbufSize, "Error: %s accepts at most %u argument%s.\n", flag_name, max_ct, (max_ct == 1)? "" : "s"); } else { - snprintf(g_logbuf, kLogbufSize, "Error: %s only accepts %u parameter%s.\n", flag_name, max_ct, (max_ct == 1)? "" : "s"); + snprintf(g_logbuf, kLogbufSize, "Error: %s only accepts %u argument%s.\n", flag_name, max_ct, (max_ct == 1)? "" : "s"); } return 1; } @@ -422,15 +442,15 @@ return 0; } if (min_ct == 1) { - snprintf(g_logbuf, kLogbufSize, "Error: Missing %s parameter.\n", flag_name); + snprintf(g_logbuf, kLogbufSize, "Error: Missing %s argument.\n", flag_name); } else { - snprintf(g_logbuf, kLogbufSize, "Error: %s requires %s%u parameters.\n", flag_name, (min_ct < max_ct)? "at least " : "", min_ct); + snprintf(g_logbuf, kLogbufSize, "Error: %s requires %s%u arguments.\n", flag_name, (min_ct < max_ct)? "at least " : "", min_ct); } return 1; } PglErr SortCmdlineFlags(uint32_t max_flag_blen, uint32_t flag_ct, char* flag_buf, uint32_t* flag_map) { - // Assumes flag_ct is the number of flag (as opposed to value) parameters, + // Assumes flag_ct is the number of flag (as opposed to value) arguments, // flag_buf[] points to a rectangular char* array (width max_flag_blen) of // flag names with leading dash(es) stripped, and flag_map[] maps flag_buf[] // entries to argv[] entries. @@ -676,6 +696,15 @@ return 0; } +BoolErr bigstack_calloc_kcp(uintptr_t ct, const char*** kcp_arr_ptr) { + *kcp_arr_ptr = S_CAST(const char**, bigstack_alloc(ct * sizeof(intptr_t))); + if (unlikely(!(*kcp_arr_ptr))) { + return 1; + } + ZeroPtrArr(ct, *kcp_arr_ptr); + return 0; +} + BoolErr bigstack_end_calloc_uc(uintptr_t ct, unsigned char** uc_arr_ptr) { *uc_arr_ptr = S_CAST(unsigned char*, bigstack_end_alloc(ct)); if (unlikely(!(*uc_arr_ptr))) { @@ -2236,13 +2265,13 @@ unsigned char* cur_name_starts_range; uint32_t last_val; uint32_t cur_val; - // two passes. first pass: count parameters, determine name_max_blen; + // two passes. first pass: count arguments, determine name_max_blen; // then allocate memory; then fill it. if (param_ct) { cur_arg_ptr = argvk[1]; while (1) { if (unlikely(ParseNextRange(argvk, param_ct, range_delim, &cur_param_idx, &cur_arg_ptr, &range_start, &rs_len, &range_end, &re_len))) { - logerrprintfww("Error: Invalid %s parameter '%s'.\n", argvk[0], argvk[cur_param_idx]); + logerrprintfww("Error: Invalid %s argument '%s'.\n", argvk[0], argvk[cur_param_idx]); logerrputs(errstr_append); return kPglRetInvalidCmdline; } @@ -2286,12 +2315,12 @@ const char* dup_check = cur_name_str; // actually a numeric check do { if (unlikely(IsNotDigit(*dup_check))) { - logerrprintfww("Error: Invalid %s parameter '%s'.\n", argvk[0], cur_name_str); + logerrprintfww("Error: Invalid %s argument '%s'.\n", argvk[0], cur_name_str); return kPglRetInvalidCmdline; } } while (*(++dup_check)); if (unlikely(ScanPosintDefcapx(cur_name_str, &cur_val))) { - logerrprintfww("Error: Invalid %s parameter '%s'.\n", argvk[0], cur_name_str); + logerrprintfww("Error: Invalid %s argument '%s'.\n", argvk[0], cur_name_str); return kPglRetInvalidCmdline; } if (range_list_ptr->starts_range[cur_param_idx]) { @@ -2311,7 +2340,7 @@ const char* dup_check = range_list_ptr->names; while (dup_check < cur_name_str) { if (unlikely(memequal(dup_check, cur_name_str, rs_len + 1))) { - logerrprintfww("Error: Duplicate %s parameter '%s'.\n", argvk[0], cur_name_str); + logerrprintfww("Error: Duplicate %s argument '%s'.\n", argvk[0], cur_name_str); return kPglRetInvalidCmdline; } dup_check = &(dup_check[name_max_blen]); @@ -2323,7 +2352,7 @@ dup_check = range_list_ptr->names; while (dup_check < cur_name_str) { if (unlikely(memequal(dup_check, cur_name_str, rs_len + 1))) { - logerrprintfww("Error: Duplicate %s parameter '%s'.\n", argvk[0], cur_name_str); + logerrprintfww("Error: Duplicate %s argument '%s'.\n", argvk[0], cur_name_str); return kPglRetInvalidCmdline; } dup_check = &(dup_check[name_max_blen]); @@ -2594,10 +2623,9 @@ idx_stop_offset = items_left; } uint32_t* hashes = ctx->hashes[parity]; - uintptr_t item_uidx = ctx->item_uidx_start[parity]; - if (idx_start_offset) { - item_uidx = FindNth1BitFrom(subset_mask, item_uidx + 1, idx_start_offset); - } + // bugfix (24 Jan 2020): IsSet(subset_mask, item_uidx) is not always true. + uintptr_t item_uidx = FindNth1BitFrom(subset_mask, ctx->item_uidx_start[parity], idx_start_offset + 1); + uintptr_t cur_bits; uintptr_t item_uidx_base; BitIter1Start(subset_mask, item_uidx, &item_uidx_base, &cur_bits); @@ -3222,7 +3250,7 @@ if (!strcmp(argvk[idx_base], permitted_modif)) { *other_idx_ptr = idx_base + 1; } else if (strcmp(argvk[idx_base + 1], permitted_modif)) { - logerrprintf("Error: Invalid %s parameter sequence.\n", argvk[0]); + logerrprintf("Error: Invalid %s argument sequence.\n", argvk[0]); return 1; } return 0; @@ -3230,7 +3258,7 @@ char ExtractCharParam(const char* ss) { // maps c, 'c', and "c" to c, and anything else to the null char. This is - // intended to support e.g. always using '#' to designate a # parameter + // intended to support e.g. always using '#' to designate a # argument // without worrying about differences between shells. const char cc = ss[0]; if (((cc == '\'') || (cc == '"')) && (ss[1]) && (ss[2] == cc) && (!ss[3])) { @@ -3245,7 +3273,7 @@ PglErr CmdlineAllocString(const char* source, const char* flag_name, uint32_t max_slen, char** sbuf_ptr) { const uint32_t slen = strlen(source); if (slen > max_slen) { - logerrprintf("Error: %s parameter too long.\n", flag_name); + logerrprintf("Error: %s argument too long.\n", flag_name); return kPglRetInvalidCmdline; } const uint32_t blen = slen + 1; @@ -3634,7 +3662,7 @@ } } if (unlikely((first_arg_idx < S_CAST(uint32_t, argc)) && (!IsCmdlineFlag(argvk[first_arg_idx])))) { - fputs("Error: First parameter must be a flag.\n", stderr); + fputs("Error: First argument must be a flag.\n", stderr); fputs(errstr_append, stderr); goto CmdlineParsePhase1_ret_INVALID_CMDLINE; } @@ -3652,8 +3680,8 @@ fputs("--help present, ignoring other flags.\n", stdout); } if ((arg_idx == S_CAST(uint32_t, argc) - 1) && flag_ct) { - // make "plink [valid flags/parameters] --help" work, and skip the - // parameters + // make "plink [valid flags/arguments] --help" work, and skip the + // arguments const char** help_argv; if (unlikely(pgl_malloc(flag_ct * sizeof(intptr_t), &help_argv))) { goto CmdlineParsePhase1_ret_NOMEM2; @@ -3675,7 +3703,7 @@ } if (strequal_k(flagname_p, "h", flagname_p_slen) || strequal_k(flagname_p, "?", flagname_p_slen)) { - // these just act like the no-parameter case + // these just act like the no-argument case fputs(ver_str, stdout); fputs(ver_str2, stdout); if ((!first_arg_idx) || (arg_idx != 1) || subst_argv) { @@ -3781,11 +3809,11 @@ } const char cc = ExtractCharParam(argvk[arg_idx + 1]); if (unlikely(!cc)) { - fputs("Error: --d parameter too long (must be a single character).\n", stderr); + fputs("Error: --d argument too long (must be a single character).\n", stderr); goto CmdlineParsePhase2_ret_INVALID_CMDLINE; } if ((cc == '-') || (cc == ',')) { - fputs("Error: --d parameter cannot be '-' or ','.\n", stderr); + fputs("Error: --d argument cannot be '-' or ','.\n", stderr); goto CmdlineParsePhase2_ret_INVALID_CMDLINE; } *range_delim_ptr = cc; @@ -3805,7 +3833,7 @@ } if (unlikely(strlen(argvk[arg_idx + 1]) > (kPglFnamesize - kMaxOutfnameExtBlen))) { fflush(stdout); - fputs("Error: --out parameter too long.\n", stderr); + fputs("Error: --out argument too long.\n", stderr); goto CmdlineParsePhase2_ret_OPEN_FAIL; } const uint32_t slen = strlen(argvk[arg_idx + 1]); diff -Nru plink2-2.00~a3-200116+dfsg/plink2_cmdline.h plink2-2.00~a3-200217+dfsg/plink2_cmdline.h --- plink2-2.00~a3-200116+dfsg/plink2_cmdline.h 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_cmdline.h 2020-02-09 04:23:08.000000000 +0000 @@ -235,6 +235,10 @@ } } +// Returns kPglRetOpenFail if file doesn't exist, or kPglRetRewindFail if file +// is process-substitution/named-pipe. Does not print an error message. +PglErr ForceNonFifo(const char* fname); + BoolErr fopen_checked(const char* fname, const char* mode, FILE** target_ptr); HEADER_INLINE IntErr putc_checked(int32_t ii, FILE* outfile) { @@ -621,6 +625,8 @@ BoolErr bigstack_calloc_cp(uintptr_t ct, char*** cp_arr_ptr); +BoolErr bigstack_calloc_kcp(uintptr_t ct, const char*** kcp_arr_ptr); + HEADER_INLINE BoolErr bigstack_calloc_c(uintptr_t ct, char** c_arr_ptr) { return bigstack_calloc_uc(ct, R_CAST(unsigned char**, c_arr_ptr)); } diff -Nru plink2-2.00~a3-200116+dfsg/plink2_common.cc plink2-2.00~a3-200217+dfsg/plink2_common.cc --- plink2-2.00~a3-200116+dfsg/plink2_common.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_common.cc 2020-02-18 02:14:09.000000000 +0000 @@ -1401,6 +1401,9 @@ char* chrtoa(const ChrInfo* cip, uint32_t chr_idx, char* buf) { // assumes chr_idx is valid if (!chr_idx) { + // TODO: probably add 'chr' in front here when output encoding calls for + // it, but wait till beta since this would technically be + // compatibility-breaking *buf++ = '0'; return buf; } diff -Nru plink2-2.00~a3-200116+dfsg/plink2_common.h plink2-2.00~a3-200217+dfsg/plink2_common.h --- plink2-2.00~a3-200116+dfsg/plink2_common.h 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_common.h 2020-02-18 00:27:24.000000000 +0000 @@ -151,36 +151,39 @@ kfExportfA = (1 << 5), kfExportfATranspose = (1 << 6), kfExportfAD = (1 << 7), - kfExportfBeagle = (1 << 8), - kfExportfBeagleNomap = (1 << 9), - kfExportfBgen11 = (1 << 10), - kfExportfBgen12 = (1 << 11), - kfExportfBgen13 = (1 << 12), - kfExportfBimbam = (1 << 13), - kfExportfBimbam1chr = (1 << 14), - kfExportfFastphase = (1 << 15), - kfExportfFastphase1chr = (1 << 16), - kfExportfHaps = (1 << 17), - kfExportfHapsLegend = (1 << 18), - kfExportfHv = (1 << 19), - kfExportfHv1chr = (1 << 20), - kfExportfIndMajorBed = (1 << 21), - kfExportfLgen = (1 << 22), - kfExportfLgenRef = (1 << 23), - kfExportfList = (1 << 24), - kfExportfRlist = (1 << 25), - kfExportfOxGen = (1 << 26), - kfExportfPed = (1 << 27), - kfExportfCompound = (1 << 28), - kfExportfStructure = (1 << 29), - kfExportfTranspose = (1 << 30), - kfExportfVcf42 = (1U << 31), - kfExportfVcf43 = (1LLU << 32), + kfExportfBcf42 = (1 << 8), + kfExportfBcf43 = (1 << 9), + kfExportfBcf = kfExportfBcf42 | kfExportfBcf43, + kfExportfBeagle = (1 << 10), + kfExportfBeagleNomap = (1 << 11), + kfExportfBgen11 = (1 << 12), + kfExportfBgen12 = (1 << 13), + kfExportfBgen13 = (1 << 14), + kfExportfBimbam = (1 << 15), + kfExportfBimbam1chr = (1 << 16), + kfExportfFastphase = (1 << 17), + kfExportfFastphase1chr = (1 << 18), + kfExportfHaps = (1 << 19), + kfExportfHapsLegend = (1 << 20), + kfExportfHv = (1 << 21), + kfExportfHv1chr = (1 << 22), + kfExportfIndMajorBed = (1 << 23), + kfExportfLgen = (1 << 24), + kfExportfLgenRef = (1 << 25), + kfExportfList = (1 << 26), + kfExportfRlist = (1 << 27), + kfExportfOxGen = (1 << 28), + kfExportfPed = (1 << 29), + kfExportfCompound = (1 << 30), + kfExportfStructure = (1U << 31), + kfExportfTranspose = (1LLU << 32), + kfExportfVcf42 = (1LLU << 33), + kfExportfVcf43 = (1LLU << 34), kfExportfVcf = kfExportfVcf42 | kfExportfVcf43, kfExportfTypemask = (2LLU * kfExportfVcf43) - kfExportf23, - kfExportfIncludeAlt = (1LLU << 33), - kfExportfBgz = (1LLU << 34), - kfExportfOmitNonmaleY = (1LLU << 35) + kfExportfIncludeAlt = (1LLU << 35), + kfExportfBgz = (1LLU << 36), + kfExportfOmitNonmaleY = (1LLU << 37) FLAGSET64_DEF_END(ExportfFlags); FLAGSET_DEF_START() @@ -1035,6 +1038,36 @@ } +HEADER_INLINE BoolErr StoreStringAtBase(unsigned char* arena_top, const char* src, uintptr_t slen, unsigned char** arena_bottom_ptr, char** dst) { + if (unlikely(slen >= S_CAST(uintptr_t, arena_top - (*arena_bottom_ptr)))) { + return 1; + } + memcpyx(*arena_bottom_ptr, src, slen, '\0'); + *dst = R_CAST(char*, *arena_bottom_ptr); + *arena_bottom_ptr += slen + 1; + return 0; +} + +HEADER_INLINE BoolErr StoreStringAtEnd(unsigned char* arena_bottom, const char* src, uintptr_t slen, unsigned char** arena_top_ptr, char** dst) { + // minor todo: verify that the tiny amount of additional safety provided by + // PtrWSubCk has no/negligible performance cost + if (PtrWSubCk(arena_bottom, slen + 1, arena_top_ptr)) { + return 1; + } + memcpyx(*arena_top_ptr, src, slen, '\0'); + *dst = R_CAST(char*, *arena_top_ptr); + return 0; +} + +HEADER_INLINE BoolErr StoreStringAtEndK(unsigned char* arena_bottom, const char* src, uintptr_t slen, unsigned char** arena_top_ptr, const char** dst) { + if (PtrWSubCk(arena_bottom, slen + 1, arena_top_ptr)) { + return 1; + } + memcpyx(*arena_top_ptr, src, slen, '\0'); + *dst = R_CAST(char*, *arena_top_ptr); + return 0; +} + // These use g_textbuf. PglErr WriteSampleIdsOverride(const uintptr_t* sample_include, const SampleIdInfo* siip, const char* outname, uint32_t sample_ct, SampleIdFlags override_flags); HEADER_INLINE PglErr WriteSampleIds(const uintptr_t* sample_include, const SampleIdInfo* siip, const char* outname, uint32_t sample_ct) { diff -Nru plink2-2.00~a3-200116+dfsg/plink2_cpu.cc plink2-2.00~a3-200217+dfsg/plink2_cpu.cc --- plink2-2.00~a3-200116+dfsg/plink2_cpu.cc 2018-05-23 03:32:50.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_cpu.cc 2020-01-25 19:31:15.000000000 +0000 @@ -125,7 +125,7 @@ return RealMain(argc, argv); CpuCheck_ret_SSE42_FAIL: fputs("Error: This plink2 build requires a processor which supports SSE4.2\ninstructions. Try a plain 64-bit build instead.\n", stderr); - exit(13); // 13 = kPglRetUnsupporedInstructions + exit(13); // 13 = kPglRetUnsupportedInstructions # ifdef CPU_CHECK_AVX2 CpuCheck_ret_AVX2_FAIL: // SSE4.2 doesn't deliver enough of an advantage to justify more clutter on diff -Nru plink2-2.00~a3-200116+dfsg/plink2_data.cc plink2-2.00~a3-200217+dfsg/plink2_data.cc --- plink2-2.00~a3-200116+dfsg/plink2_data.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_data.cc 2020-02-09 00:56:31.000000000 +0000 @@ -354,7 +354,7 @@ if (pvar_info_reload) { reterr = PvarInfoOpenAndReloadHeader(pvar_info_reload, 1 + (thread_ct > 1), &pvar_reload_txs, &pvar_info_line_iter, &info_col_idx); if (unlikely(reterr)) { - goto WritePvar_ret_TSTREAM_REWIND_FAIL; + goto WritePvar_ret_TSTREAM_FAIL; } } if (cip->chrset_source) { @@ -498,7 +498,7 @@ if (pvar_info_line_iter) { reterr = PvarInfoReloadAndWrite(info_pr_flag_present, info_col_idx, variant_uidx, is_pr, &pvar_reload_txs, &pvar_info_line_iter, &cswritep, &trs_variant_uidx); if (unlikely(reterr)) { - goto WritePvar_ret_TSTREAM_REWIND_FAIL; + goto WritePvar_ret_TSTREAM_FAIL; } } else { if (is_pr) { @@ -540,8 +540,8 @@ WritePvar_ret_NOMEM: reterr = kPglRetNomem; break; - WritePvar_ret_TSTREAM_REWIND_FAIL: - TextStreamErrPrintRewind(pvar_info_reload, &pvar_reload_txs, &reterr); + WritePvar_ret_TSTREAM_FAIL: + TextStreamErrPrint(pvar_info_reload, &pvar_reload_txs); break; WritePvar_ret_WRITE_FAIL: reterr = kPglRetWriteFail; @@ -3434,7 +3434,7 @@ } reterr = PvarInfoOpenAndReloadHeader(pvar_info_reload, 1 + (thread_ct > 1), &pvar_reload_txs, &pvar_info_line_iter, &info_col_idx); if (unlikely(reterr)) { - goto WritePvarSplit_ret_TSTREAM_REWIND_FAIL; + goto WritePvarSplit_ret_TSTREAM_FAIL; } } if (cip->chrset_source) { @@ -3574,7 +3574,7 @@ if ((split_ct_p1 != 2) && pvar_info_line_iter) { reterr = PvarInfoReload(info_col_idx, variant_uidx, &pvar_reload_txs, &pvar_info_line_iter, &trs_variant_uidx); if (unlikely(reterr)) { - goto WritePvarSplit_ret_TSTREAM_REWIND_FAIL; + goto WritePvarSplit_ret_TSTREAM_FAIL; } char* info_subtoken_iter = pvar_info_line_iter; pvar_info_line_iter = CurTokenEnd(pvar_info_line_iter); @@ -3715,7 +3715,7 @@ if (split_ct_p1 == 2) { reterr = PvarInfoReloadAndWrite(info_pr_flag_present, info_col_idx, variant_uidx, is_pr, &pvar_reload_txs, &pvar_info_line_iter, &cswritep, &trs_variant_uidx); if (unlikely(reterr)) { - goto WritePvarSplit_ret_TSTREAM_REWIND_FAIL; + goto WritePvarSplit_ret_TSTREAM_FAIL; } } else { if (!cur_info_key_ct) { @@ -3813,8 +3813,8 @@ WritePvarSplit_ret_NOMEM: reterr = kPglRetNomem; break; - WritePvarSplit_ret_TSTREAM_REWIND_FAIL: - TextStreamErrPrintRewind(pvar_info_reload, &pvar_reload_txs, &reterr); + WritePvarSplit_ret_TSTREAM_FAIL: + TextStreamErrPrint(pvar_info_reload, &pvar_reload_txs); break; WritePvarSplit_ret_WRITE_FAIL: reterr = kPglRetWriteFail; @@ -3875,12 +3875,10 @@ for (uint32_t hashval = Hash32(filter_iter, cur_id_slen) >> hash_shift; ; ) { char* cur_token_ptr = filter_tokens[hashval]; if (!cur_token_ptr) { - char* storage_loc = R_CAST(char*, tmp_alloc_base); - tmp_alloc_base = &(tmp_alloc_base[cur_id_slen + 1]); - if (unlikely(tmp_alloc_base > tmp_alloc_end)) { + char* storage_loc; + if (StoreStringAtBase(tmp_alloc_end, filter_iter, cur_id_slen, &tmp_alloc_base, &storage_loc)) { goto MakeFilterHtable_ret_NOMEM; } - memcpyx(storage_loc, filter_iter, cur_id_slen, '\0'); ++filter_key_ct; if (filter_key_ct * 4 < table_size) { filter_tokens[hashval] = storage_loc; @@ -4141,7 +4139,7 @@ } reterr = PvarInfoOpenAndReloadHeader(pvar_info_reload, 1 + (thread_ct > 1), &pvar_reload_txs, &pvar_info_line_iter, &info_col_idx); if (unlikely(reterr)) { - goto WritePvarJoin_ret_TSTREAM_REWIND_FAIL; + goto WritePvarJoin_ret_TSTREAM_FAIL; } } if (write_info) { @@ -4312,7 +4310,7 @@ if (pvar_info_line_iter) { reterr = PvarInfoReloadAndWrite(info_pr_flag_present, info_col_idx, variant_uidx, is_pr, &pvar_reload_txs, &pvar_info_line_iter, &cswritep, &trs_variant_uidx); if (unlikely(reterr)) { - goto WritePvar_ret_TSTREAM_REWIND_FAIL; + goto WritePvar_ret_TSTREAM_FAIL; } } else { if (is_pr) { @@ -4392,8 +4390,8 @@ WritePvarJoin_ret_NOMEM: reterr = kPglRetNomem; break; - WritePvarJoin_ret_TSTREAM_REWIND_FAIL: - TextStreamErrPrintRewind(pvar_info_reload, &pvar_reload_txs, &reterr); + WritePvarJoin_ret_TSTREAM_FAIL: + TextStreamErrPrint(pvar_info_reload, &pvar_reload_txs, &reterr); break; WritePvarJoin_ret_WRITE_FAIL: reterr = kPglRetWriteFail; @@ -7565,7 +7563,7 @@ } reterr = SizeAndInitTextStream(pvar_info_reload, bigstack_left() / 4, decompress_thread_ct, &pvar_reload_txs); if (unlikely(reterr)) { - goto WritePvarResorted_ret_TSTREAM_REWIND_FAIL; + goto WritePvarResorted_ret_TSTREAM_FAIL; } // subtract kCacheline to allow for rounding @@ -7600,7 +7598,7 @@ if (pvar_info_reload) { reterr = PvarInfoReloadInterval(old_variant_uidx_to_new, variant_idx_start, variant_idx_end, &pvar_reload_txs, pvar_info_strs); if (unlikely(reterr)) { - goto WritePvarResorted_ret_TSTREAM_REWIND_FAIL; + goto WritePvarResorted_ret_TSTREAM_FAIL; } } reterr = WritePvarResortedInterval(write_cip, variant_bps, variant_ids, allele_idx_offsets, allele_storage, allele_presents, refalt1_select, qual_present, quals, filter_present, filter_npass, filter_storage, nonref_flags, variant_cms, new_variant_idx_to_old, variant_idx_start, variant_idx_end, info_pr_flag_present, write_qual, write_filter, write_info, all_nonref, write_cm, pvar_info_strs, &css, &cswritep, &chr_fo_idx, &chr_end, &chr_buf_blen, chr_buf); @@ -7622,8 +7620,8 @@ WritePvarResorted_ret_NOMEM: reterr = kPglRetNomem; break; - WritePvarResorted_ret_TSTREAM_REWIND_FAIL: - TextStreamErrPrintRewind(pvar_info_reload, &pvar_reload_txs, &reterr); + WritePvarResorted_ret_TSTREAM_FAIL: + TextStreamErrPrint(pvar_info_reload, &pvar_reload_txs); break; WritePvarResorted_ret_WRITE_FAIL: reterr = kPglRetWriteFail; diff -Nru plink2-2.00~a3-200116+dfsg/plink2_export.cc plink2-2.00~a3-200217+dfsg/plink2_export.cc --- plink2-2.00~a3-200116+dfsg/plink2_export.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_export.cc 2020-02-18 02:29:13.000000000 +0000 @@ -162,14 +162,9 @@ allele_missing[variant_uidx] = &(g_one_char_strs[2 * ctou32(allele_char)]); continue; } - // no underflow danger since allele must fit in line-buffer, which is - // positioned earlier in bigstack - tmp_alloc_end -= allele_slen + 1; - if (unlikely(tmp_alloc_end < tmp_alloc_base)) { + if (StoreStringAtEndK(tmp_alloc_base, allele_start, allele_slen, &tmp_alloc_end, &(allele_missing[variant_uidx]))) { goto ExportAlleleLoad_ret_NOMEM; } - memcpyx(tmp_alloc_end, allele_start, allele_slen, '\0'); - allele_missing[variant_uidx] = R_CAST(char*, tmp_alloc_end); } if (duplicate_ct) { logerrprintfww("Warning: %" PRIuPTR " duplicate variant ID%s in --export-allele file (not an error since allele%s consistent).\n", duplicate_ct, (duplicate_ct == 1)? "" : "s", (duplicate_ct == 1)? " was" : "s were"); @@ -4368,9 +4363,1919 @@ const uint32_t info_pr_flag_present = (info_flags / kfInfoPrFlagPresent) & 1; if (write_pr) { if (unlikely(info_flags & kfInfoPrNonflagPresent)) { - logputs("\n"); logerrputs("Error: Conflicting INFO:PR definitions. Either fix all REF alleles so that the\n'provisional reference' flag is no longer needed, or remove/rename the other\nuse of the INFO:PR key.\n"); - goto ExportVcf_ret_INCONSISTENT_INPUT; + goto ExportVcf_ret_INCONSISTENT_INPUT; + } + if (!info_pr_flag_present) { + write_iter = strcpya_k(write_iter, "##INFO=" EOLN_STR); + } + } + if (write_ds) { + write_iter = strcpya_k(write_iter, "##FORMAT=" EOLN_STR); + if (write_hds) { + // bugfix (3 May 2018): 'Number=2' was inaccurate for haploid calls. + + // Note that HDS ploidy intentionally does NOT match GT ploidy in the + // unphased het haploid case. + write_iter = strcpya_k(write_iter, "##FORMAT=" EOLN_STR); + } + } else if (write_some_dosage) { + write_iter = strcpya_k(write_iter, "##FORMAT=" EOLN_STR); + } + // possible todo: optionally export .psam information as + // PEDIGREE/META/SAMPLE lines in header, and make --vcf be able to read it + write_iter = strcpya_k(write_iter, "##FORMAT=" EOLN_STR "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\tFORMAT"); + char* exported_sample_ids; + uint32_t* exported_id_htable; + uintptr_t max_exported_sample_id_blen; + if (unlikely(ExportIdpaste(sample_include, siip, "vcf", sample_ct, exportf_id_paste, exportf_id_delim, &max_exported_sample_id_blen, &exported_sample_ids, &exported_id_htable))) { + goto ExportVcf_ret_NOMEM; + } + for (uint32_t sample_idx = 0; sample_idx != sample_ct; ++sample_idx) { + *write_iter++ = '\t'; + write_iter = strcpya(write_iter, &(exported_sample_ids[sample_idx * max_exported_sample_id_blen])); + if (unlikely(bgzfwrite_ck(writebuf_flush, &bgzf, &write_iter))) { + goto ExportVcf_ret_WRITE_FAIL; + } + } + AppendBinaryEoln(&write_iter); + BigstackReset(exported_sample_ids); + + logprintfww5("--export vcf%s to %s ... ", (exportf_flags & kfExportfBgz)? " bgz" : "", outname); + fputs("0%", stdout); + fflush(stdout); + + // includes trailing tab + char* chr_buf; + + const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); + const uint32_t sample_ctl = BitCtToWordCt(sample_ct); + PgenVariant pgv; + if (unlikely( + bigstack_alloc_c(max_chr_blen, &chr_buf) || + // if we weren't using bigstack_alloc, this would need to be + // sample_ctaw2 + bigstack_alloc_w(sample_ctl2, &pgv.genovec))) { + goto ExportVcf_ret_NOMEM; + } + pgv.patch_01_set = nullptr; + pgv.patch_01_vals = nullptr; + pgv.patch_10_set = nullptr; + pgv.patch_10_vals = nullptr; + if (allele_idx_offsets) { + if (unlikely( + bigstack_alloc_w(sample_ctl, &(pgv.patch_01_set)) || + bigstack_alloc_ac(sample_ct, &(pgv.patch_01_vals)) || + bigstack_alloc_w(sample_ctl, &(pgv.patch_10_set)) || + bigstack_alloc_ac(sample_ct * 2, &(pgv.patch_10_vals)))) { + goto ExportVcf_ret_NOMEM; + } + } + // For now, if phased data is present, each homozygous call is represented + // as phased iff the previous heterozygous call was phased. (If no + // previous heterozygous call exists, it's treated as phased.) This does + // the right thing when the entire genome is phased, and it induces about + // as good a phase set approximation as you can get without explicitly + // saving that info. But that approximation is still pretty inaccurate; as + // soon as we have any use for them, explicit phase set support should be + // added to pgenlib. + const uint32_t load_dphase = write_hds && (pgfip->gflags & kfPgenGlobalDosagePhasePresent); + const uint32_t some_phased = (pgfip->gflags & kfPgenGlobalHardcallPhasePresent) || load_dphase; + uintptr_t* prev_phased = nullptr; + pgv.phasepresent = nullptr; + pgv.phaseinfo = nullptr; + if (some_phased) { + if (unlikely( + bigstack_alloc_w(sample_ctl, &prev_phased) || + bigstack_alloc_w(sample_ctl, &(pgv.phasepresent)) || + bigstack_alloc_w(sample_ctl, &(pgv.phaseinfo)))) { + goto ExportVcf_ret_NOMEM; + } + SetAllBits(sample_ct, prev_phased); + } + + pgv.dosage_present = nullptr; + pgv.dosage_main = nullptr; + pgv.dphase_present = nullptr; + pgv.dphase_delta = nullptr; + if (write_some_dosage && (pgfip->gflags & kfPgenGlobalDosagePresent)) { + if (unlikely( + bigstack_alloc_w(sample_ctl, &(pgv.dosage_present)) || + bigstack_alloc_dosage(sample_ct, &(pgv.dosage_main)))) { + goto ExportVcf_ret_NOMEM; + } + if (load_dphase) { + if (unlikely( + bigstack_alloc_w(sample_ctl, &(pgv.dphase_present)) || + bigstack_alloc_dphase(sample_ct, &(pgv.dphase_delta)))) { + goto ExportVcf_ret_NOMEM; + } + } + } + + char* pvar_reload_line_iter = nullptr; + uint32_t info_col_idx = 0; + if (pvar_info_reload) { + reterr = PvarInfoOpenAndReloadHeader(pvar_info_reload, 1 + (max_thread_ct > 1), &pvar_reload_txs, &pvar_reload_line_iter, &info_col_idx); + if (unlikely(reterr)) { + goto ExportVcf_ret_TSTREAM_FAIL; + } + } + + // assumes little-endian + // maybe want to pass references to these arrays later, but leave as + // C-style for now + // \t0/0 \t0/1 \t1/1 \t./. + const uint32_t basic_genotext[4] = {0x302f3009, 0x312f3009, 0x312f3109, 0x2e2f2e09}; + + // 4-genotype-at-a-time lookup in basic case + uint32_t basic_genotext4[1024] ALIGNV16; + basic_genotext4[0] = basic_genotext[0]; + basic_genotext4[4] = basic_genotext[1]; + basic_genotext4[8] = basic_genotext[2]; + basic_genotext4[12] = basic_genotext[3]; + InitLookup256x4bx4(basic_genotext4); + + uint32_t haploid_genotext_blen[8]; + // lengths [0], [2], and [3] reset at beginning of chromosome + // chrX: nonmales are diploid and use indices 0..3, males are 4..7 + // other: all haploid, indices 0..3 + haploid_genotext_blen[1] = 4; + haploid_genotext_blen[4] = 2; + haploid_genotext_blen[5] = 4; + haploid_genotext_blen[6] = 2; + haploid_genotext_blen[7] = 2; + // don't bother exporting GP for hardcalls + // usually don't bother for DS, but DS-force is an exception + + // 4..7 = haploid, 5 should never be looked up + // :0 :1 :2 :. :0 !! :1 :. + const uint16_t ds_inttext[8] = {0x303a, 0x313a, 0x323a, 0x2e3a, 0x303a, 0x2121, 0x313a, 0x2e3a}; + + // 0..3 = diploid unphased + // 4..5 = phased, phaseinfo=0 first + // update (1 Aug 2018): HDS ploidy should be more trustworthy than GT + // ploidy when there's a conflict, since GT must render unphased het + // haploids as 0/1. So under HDS-force, diploid missing value is '.,.' + // instead of '.'. (Since we're only interested in storing a trustworthy + // ploidy, we don't add more missing entries in the multiallelic case.) + + // could make this include DS text in front + // could expand this to e.g. 10 cases and make [4], [5] less fiddly + // :0,0 :0.5,0.5 :1,1 :.,. :0,1 :1,0 + const uint64_t hds_inttext[6] = {0x302c303a, 0x352e302c352e303aLLU, 0x312c313a, 0x2e2c2e3a, 0x312c303a, 0x302c313a}; + + // [4]..[7] = haploid unphased; == 4 for phased + uint32_t hds_inttext_blen[8]; + + // lengths [0]-[3] reset at beginning of chromosome + hds_inttext_blen[4] = 2; + hds_inttext_blen[5] = 4; + hds_inttext_blen[6] = 2; + hds_inttext_blen[7] = 2; + const char* dot_ptr = &(g_one_char_strs[92]); + const uint32_t sample_ctl2_m1 = sample_ctl2 - 1; + PgrSampleSubsetIndex pssi; + PgrSetSampleSubsetIndex(sample_include_cumulative_popcounts, simple_pgrp, &pssi); + uintptr_t variant_uidx_base = 0; + uintptr_t cur_bits = variant_include[0]; + uint32_t chr_fo_idx = UINT32_MAX; + uint32_t chr_end = 0; + uint32_t chr_buf_blen = 0; + uint32_t is_x = 0; + uint32_t is_haploid = 0; // includes chrX and chrY + uint32_t pct = 0; + uint32_t next_print_variant_idx = variant_ct / 100; + uint32_t rls_variant_uidx = 0; + uint32_t ref_allele_idx = 0; + uint32_t alt1_allele_idx = 1; + uint32_t allele_ct = 2; + uint32_t invalid_allele_code_seen = 0; + for (uint32_t variant_idx = 0; variant_idx != variant_ct; ++variant_idx) { + // a lot of this is redundant with write_pvar(), may want to factor the + // commonalities out + const uint32_t variant_uidx = BitIter1(variant_include, &variant_uidx_base, &cur_bits); + if (variant_uidx >= chr_end) { + do { + ++chr_fo_idx; + chr_end = cip->chr_fo_vidx_start[chr_fo_idx + 1]; + } while (variant_uidx >= chr_end); + uint32_t chr_idx = cip->chr_file_order[chr_fo_idx]; + is_x = (chr_idx == cip->xymt_codes[kChrOffsetX]); + is_haploid = IsSet(cip->haploid_mask, chr_idx); + // forced --merge-par, with diploid male output (is_x NOT set, but + // chromosome code is X/chrX) + if ((chr_idx == cip->xymt_codes[kChrOffsetPAR1]) || (chr_idx == cip->xymt_codes[kChrOffsetPAR2])) { + chr_idx = cip->xymt_codes[kChrOffsetX]; + } + char* chr_name_end = chrtoa(cip, chr_idx, chr_buf); + *chr_name_end = '\t'; + chr_buf_blen = 1 + S_CAST(uintptr_t, chr_name_end - chr_buf); + // bugfix (3 May 2018): forgot to update hds_inttext_blen[] + hds_inttext_blen[0] = 4; + hds_inttext_blen[1] = 8; + hds_inttext_blen[2] = 4; + hds_inttext_blen[3] = 4; + if (is_haploid) { + if (is_x) { + haploid_genotext_blen[0] = 4; + haploid_genotext_blen[2] = 4; + haploid_genotext_blen[3] = 4; + } else { + haploid_genotext_blen[0] = 2; + haploid_genotext_blen[2] = 2; + haploid_genotext_blen[3] = 2; + hds_inttext_blen[0] = 2; + hds_inttext_blen[1] = 4; + hds_inttext_blen[2] = 2; + hds_inttext_blen[3] = 2; + } + } + } + // #CHROM + write_iter = memcpya(write_iter, chr_buf, chr_buf_blen); + + // POS + write_iter = u32toa_x(variant_bps[variant_uidx], '\t', write_iter); + + // ID + write_iter = strcpyax(write_iter, variant_ids[variant_uidx], '\t'); + + // REF, ALT + uintptr_t allele_idx_offset_base = variant_uidx * 2; + if (allele_idx_offsets) { + allele_idx_offset_base = allele_idx_offsets[variant_uidx]; + allele_ct = allele_idx_offsets[variant_uidx + 1] - allele_idx_offset_base; + } + const char* const* cur_alleles = &(allele_storage[allele_idx_offset_base]); + if (refalt1_select) { + ref_allele_idx = refalt1_select[variant_uidx][0]; + alt1_allele_idx = refalt1_select[variant_uidx][1]; + } + if (cur_alleles[ref_allele_idx] != dot_ptr) { + write_iter = strcpya(write_iter, cur_alleles[ref_allele_idx]); + if (!invalid_allele_code_seen) { + invalid_allele_code_seen = !ValidVcfAlleleCode(cur_alleles[ref_allele_idx]); + } + } else { + *write_iter++ = 'N'; + } + *write_iter++ = '\t'; + write_iter = strcpya(write_iter, cur_alleles[alt1_allele_idx]); + if (!invalid_allele_code_seen) { + invalid_allele_code_seen = !ValidVcfAlleleCode(cur_alleles[alt1_allele_idx]); + } + if (unlikely(bgzfwrite_ck(writebuf_flush, &bgzf, &write_iter))) { + goto ExportVcf_ret_WRITE_FAIL; + } + if (allele_ct > 2) { + for (uint32_t cur_allele_uidx = 0; cur_allele_uidx != allele_ct; ++cur_allele_uidx) { + if ((cur_allele_uidx == ref_allele_idx) || (cur_allele_uidx == alt1_allele_idx)) { + // if this is noticeably suboptimal, have two loops, with inner + // loop going up to cur_allele_stop. + // (also wrap this in a function, this comes up a bunch of times) + continue; + } + *write_iter++ = ','; + write_iter = strcpya(write_iter, cur_alleles[cur_allele_uidx]); + if (!invalid_allele_code_seen) { + invalid_allele_code_seen = !ValidVcfAlleleCode(cur_alleles[cur_allele_uidx]); + } + if (unlikely(bgzfwrite_ck(writebuf_flush, &bgzf, &write_iter))) { + goto ExportVcf_ret_WRITE_FAIL; + } + } + } + + // QUAL + *write_iter++ = '\t'; + if ((!pvar_qual_present) || (!IsSet(pvar_qual_present, variant_uidx))) { + *write_iter++ = '.'; + } else { + write_iter = ftoa_g(pvar_quals[variant_uidx], write_iter); + } + + // FILTER + *write_iter++ = '\t'; + if ((!pvar_filter_present) || (!IsSet(pvar_filter_present, variant_uidx))) { + *write_iter++ = '.'; + } else if (!IsSet(pvar_filter_npass, variant_uidx)) { + write_iter = strcpya_k(write_iter, "PASS"); + } else { + write_iter = strcpya(write_iter, pvar_filter_storage[variant_uidx]); + } + + // INFO + *write_iter++ = '\t'; + const uint32_t is_pr = all_nonref || (nonref_flags && IsSet(nonref_flags, variant_uidx)); + if (pvar_reload_line_iter) { + reterr = PvarInfoReloadAndWrite(info_pr_flag_present, info_col_idx, variant_uidx, is_pr, &pvar_reload_txs, &pvar_reload_line_iter, &write_iter, &rls_variant_uidx); + if (unlikely(reterr)) { + goto ExportVcf_ret_TSTREAM_FAIL; + } + } else { + if (is_pr) { + write_iter = strcpya_k(write_iter, "PR"); + } else { + *write_iter++ = '.'; + } + } + + // FORMAT + write_iter = strcpya_k(write_iter, "\tGT"); + + // could defensively zero out more counts + pgv.dosage_ct = 0; + pgv.dphase_ct = 0; + uint32_t inner_loop_last = kBitsPerWordD2 - 1; + if (allele_ct == 2) { + if (!some_phased) { + // biallelic, nothing phased in entire file + // (technically possible for dosage-phase to be present, if no + // hardcalls are phased and HDS output not requested) + if (!write_some_dosage) { + reterr = PgrGet(sample_include, pssi, sample_ct, variant_uidx, simple_pgrp, pgv.genovec); + } else { + reterr = PgrGetD(sample_include, pssi, sample_ct, variant_uidx, simple_pgrp, pgv.genovec, pgv.dosage_present, pgv.dosage_main, &(pgv.dosage_ct)); + } + if (unlikely(reterr)) { + goto ExportVcf_ret_PGR_FAIL; + } + if (!alt1_allele_idx) { + // assumes biallelic + GenovecInvertUnsafe(sample_ct, pgv.genovec); + if (pgv.dosage_ct) { + BiallelicDosage16Invert(pgv.dosage_ct, pgv.dosage_main); + } + } + if ((!pgv.dosage_ct) && (!ds_force)) { + if (!is_haploid) { + // always 4 bytes wide, exploit that + GenoarrLookup256x4bx4(pgv.genovec, basic_genotext4, sample_ct, write_iter); + write_iter = &(write_iter[sample_ct * 4]); + } else { + // chrX: male homozygous/missing calls use only one character + + // tab + // other haploid/MT: this is true for nonmales too + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + uint32_t sex_male_hw = is_x * (R_CAST(const Halfword*, sex_male_collapsed)[widx]); + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + const uint32_t cur_is_male = sex_male_hw & 1; + memcpy(write_iter, &(basic_genotext[cur_geno]), 4); + write_iter = &(write_iter[haploid_genotext_blen[cur_geno + cur_is_male * 4]]); + genovec_word >>= 2; + sex_male_hw >>= 1; + } + } + } + } else { + // some dosages present, or {H}DS-force; unphased + if (write_ds) { + write_iter = strcpya_k(write_iter, ":DS"); + if (hds_force) { + write_iter = strcpya_k(write_iter, ":HDS"); + } + } else { + write_iter = strcpya_k(write_iter, ":GP"); + } + Dosage* dosage_main_iter = pgv.dosage_main; + uint32_t dosage_present_hw = 0; + if (!is_haploid) { + // autosomal diploid, unphased + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + if (pgv.dosage_ct) { + dosage_present_hw = R_CAST(Halfword*, pgv.dosage_present)[widx]; + } + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + write_iter = memcpya(write_iter, &(basic_genotext[cur_geno]), 4); + if (dosage_present_hw & 1) { + *write_iter++ = ':'; + const uint32_t dosage_int = *dosage_main_iter++; + write_iter = PrintDiploidVcfDosage(dosage_int, write_ds, write_iter); + if (hds_force) { + *write_iter++ = ':'; + char* write_iter2 = PrintHaploidNonintDosage(dosage_int, write_iter); + write_iter2[0] = ','; + write_iter = memcpya(&(write_iter2[1]), write_iter, write_iter2 - write_iter); + } + } else if (ds_force) { + write_iter = memcpya_k(write_iter, &(ds_inttext[cur_geno]), 2); + if (hds_force) { + memcpy(write_iter, &(hds_inttext[cur_geno]), 8); + write_iter = &(write_iter[hds_inttext_blen[cur_geno]]); + } + } + genovec_word >>= 2; + dosage_present_hw >>= 1; + } + } + } else { + // at least partly haploid, unphased + uint32_t sex_male_hw = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + if (is_x) { + sex_male_hw = R_CAST(const Halfword*, sex_male_collapsed)[widx]; + } + if (pgv.dosage_ct) { + dosage_present_hw = R_CAST(Halfword*, pgv.dosage_present)[widx]; + } + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + const uint32_t cur_is_male = sex_male_hw & 1; + const uint32_t cur_genotext_blen = haploid_genotext_blen[cur_geno + cur_is_male * 4]; + memcpy(write_iter, &(basic_genotext[cur_geno]), 4); + write_iter = &(write_iter[cur_genotext_blen]); + if (dosage_present_hw & 1) { + *write_iter++ = ':'; + uint32_t dosage_int = *dosage_main_iter++; + if (cur_genotext_blen == 2) { + // render current hardcall as haploid + if (write_ds) { + char* write_iter2 = PrintHaploidNonintDosage(dosage_int, write_iter); + if (hds_force) { + write_iter2[0] = ':'; + write_iter2 = memcpya(&(write_iter2[1]), write_iter, write_iter2 - write_iter); + } + write_iter = write_iter2; + } else { + // GP + write_iter = PrintHaploidNonintDosage(kDosageMax - dosage_int, write_iter); + *write_iter++ = ','; + write_iter = PrintHaploidNonintDosage(dosage_int, write_iter); + } + } else { + // render current hardcall as diploid (female X, or het + // haploid) + write_iter = PrintDiploidVcfDosage(dosage_int, write_ds, write_iter); + if (hds_force) { + *write_iter++ = ':'; + char* write_iter2 = PrintHaploidNonintDosage(dosage_int, write_iter); + if (is_x && (!cur_is_male)) { + // but do not render phased-dosage as diploid in het + // haploid case + write_iter2[0] = ','; + write_iter2 = memcpya(&(write_iter2[1]), write_iter, write_iter2 - write_iter); + } + write_iter = write_iter2; + } + } + } else if (ds_force) { + write_iter = memcpya_k(write_iter, &(ds_inttext[cur_geno + 8 - 2 * cur_genotext_blen]), 2); + if (hds_force) { + memcpy(write_iter, &(hds_inttext[cur_geno]), 8); + write_iter = &(write_iter[hds_inttext_blen[cur_is_male * 4 + cur_geno]]); + } + } + genovec_word >>= 2; + sex_male_hw >>= 1; + dosage_present_hw >>= 1; + } + } + } + } + } else { + // biallelic, phased + if (!write_some_dosage) { + reterr = PgrGetP(sample_include, pssi, sample_ct, variant_uidx, simple_pgrp, pgv.genovec, pgv.phasepresent, pgv.phaseinfo, &(pgv.phasepresent_ct)); + } else { + reterr = PgrGetDp(sample_include, pssi, sample_ct, variant_uidx, simple_pgrp, &pgv); + } + if (unlikely(reterr)) { + goto ExportVcf_ret_PGR_FAIL; + } + if (!alt1_allele_idx) { + // assumes biallelic + GenovecInvertUnsafe(sample_ct, pgv.genovec); + if (pgv.phasepresent_ct) { + BitvecInvert(sample_ctl, pgv.phaseinfo); + } + if (pgv.dosage_ct) { + BiallelicDosage16Invert(pgv.dosage_ct, pgv.dosage_main); + if (pgv.dphase_ct) { + BiallelicDphase16Invert(pgv.dphase_ct, pgv.dphase_delta); + } + } + } + uint32_t phasepresent_hw = 0; + if ((!pgv.dosage_ct) && (!ds_force)) { + if (!is_haploid) { + // can't just use PhaseLookup4b(), thanks to prev_phased_halfword + uint32_t* write_iter_u32_alias = R_CAST(uint32_t*, write_iter); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + uint32_t prev_phased_halfword = R_CAST(Halfword*, prev_phased)[widx]; + + if (pgv.phasepresent_ct) { + phasepresent_hw = R_CAST(Halfword*, pgv.phasepresent)[widx]; + } + const uint32_t phaseinfo_hw = R_CAST(Halfword*, pgv.phaseinfo)[widx]; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uintptr_t cur_geno = genovec_word & 3; + + // usually "\t0/0", etc. + uint32_t cur_basic_genotext = basic_genotext[cur_geno]; + if (cur_geno == 1) { + const uint32_t cur_shift = (1U << sample_idx_lowbits); + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + cur_basic_genotext ^= 0x1000100; // 0|1 -> 1|0 + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } + // '/' = ascii 47, '|' = ascii 124 + *write_iter_u32_alias++ = cur_basic_genotext + 0x4d0000 * ((prev_phased_halfword >> sample_idx_lowbits) & 1); + genovec_word >>= 2; + } + R_CAST(Halfword*, prev_phased)[widx] = prev_phased_halfword; + } + write_iter = R_CAST(char*, write_iter_u32_alias); + } else { + uint32_t is_male_hw = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + if (is_x) { + is_male_hw = R_CAST(const Halfword*, sex_male_collapsed)[widx]; + } + uint32_t prev_phased_halfword = R_CAST(Halfword*, prev_phased)[widx]; + + if (pgv.phasepresent_ct) { + phasepresent_hw = R_CAST(Halfword*, pgv.phasepresent)[widx]; + } + + const uint32_t phaseinfo_hw = R_CAST(Halfword*, pgv.phaseinfo)[widx]; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + const uint32_t cur_is_male = is_male_hw & 1; + const uint32_t cur_blen = haploid_genotext_blen[cur_geno + cur_is_male * 4]; + memcpy(write_iter, &(basic_genotext[cur_geno]), 4); + write_iter = &(write_iter[cur_blen]); + if (cur_blen == 4) { + if (cur_geno == 1) { + // a bit redundant with how is_male_hw is handled, but + // updating this on every loop iteration doesn't seem + // better + const uint32_t cur_shift = (1U << sample_idx_lowbits); + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + memcpy(&(write_iter[-4]), "\t1|0", 4); + } else { + write_iter[-2] = '|'; + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } else if ((prev_phased_halfword >> sample_idx_lowbits) & 1) { + write_iter[-2] = '|'; + } + } + genovec_word >>= 2; + is_male_hw >>= 1; + } + R_CAST(Halfword*, prev_phased)[widx] = prev_phased_halfword; + } + } + } else { + // both dosage (or {H}DS-force) and phase present + if (write_ds) { + write_iter = strcpya_k(write_iter, ":DS"); + if (hds_force || pgv.dphase_ct || + (write_hds && pgv.phasepresent_ct && pgv.dosage_ct && (!IntersectionIsEmpty(pgv.phasepresent, pgv.dosage_present, sample_ctl)))) { + write_iter = strcpya_k(write_iter, ":HDS"); + // no need to clear dphase_present, since we zero-initialize + // dphase_present_hw and never refresh it when dphase_ct == 0 + } + } else { + write_iter = strcpya_k(write_iter, ":GP"); + } + Dosage* dosage_main_iter = pgv.dosage_main; + SDosage* dphase_delta_iter = pgv.dphase_delta; + uint32_t dosage_present_hw = 0; + uint32_t dphase_present_hw = 0; + if (!is_haploid) { + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + uint32_t prev_phased_halfword = R_CAST(Halfword*, prev_phased)[widx]; + + if (pgv.phasepresent_ct) { + phasepresent_hw = R_CAST(Halfword*, pgv.phasepresent)[widx]; + } + + const uint32_t phaseinfo_hw = R_CAST(Halfword*, pgv.phaseinfo)[widx]; + if (pgv.dosage_ct) { + dosage_present_hw = R_CAST(Halfword*, pgv.dosage_present)[widx]; + if (pgv.dphase_ct) { + dphase_present_hw = R_CAST(Halfword*, pgv.dphase_present)[widx]; + } + } + uint32_t cur_shift = 1; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + write_iter = memcpya(write_iter, &(basic_genotext[cur_geno]), 4); + if (cur_geno == 1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + memcpy(&(write_iter[-4]), "\t1|0", 4); + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } + if (prev_phased_halfword & cur_shift) { + write_iter[-2] = '|'; + } + if (dosage_present_hw & cur_shift) { + *write_iter++ = ':'; + const uint32_t dosage_int = *dosage_main_iter++; + write_iter = PrintDiploidVcfDosage(dosage_int, write_ds, write_iter); + // bugfix (29 May 2018): don't print HDS field if not + // requested + if (write_hds) { + if ((phasepresent_hw | dphase_present_hw) & cur_shift) { + int32_t cur_dphase_delta; + if (dphase_present_hw & cur_shift) { + cur_dphase_delta = *dphase_delta_iter++; + } else { + cur_dphase_delta = DosageHomdist(dosage_int); + if (!(phaseinfo_hw & cur_shift)) { + cur_dphase_delta = -cur_dphase_delta; + } + } + *write_iter++ = ':'; + write_iter = PrintHdsPair(dosage_int, cur_dphase_delta, write_iter); + } else if (hds_force) { + *write_iter++ = ':'; + char* write_iter2 = PrintHaploidNonintDosage(dosage_int, write_iter); + write_iter2[0] = ','; + write_iter = memcpya(&(write_iter2[1]), write_iter, write_iter2 - write_iter); + } + } + } else if (ds_force) { + write_iter = memcpya_k(write_iter, &(ds_inttext[cur_geno]), 2); + if (hds_force) { + uint32_t hds_inttext_index = cur_geno; + uint32_t tmp_blen = hds_inttext_blen[cur_geno]; + if (phasepresent_hw & cur_shift) { + // do we want to remove this branch? doubt it's + // worthwhile since variable-length memcpy will branch + // anyway... + hds_inttext_index = 4 + ((phaseinfo_hw >> sample_idx_lowbits) & 1); + tmp_blen = 4; + } + memcpy(write_iter, &(hds_inttext[hds_inttext_index]), 8); + write_iter = &(write_iter[tmp_blen]); + } + } + genovec_word >>= 2; + cur_shift <<= 1; + } + R_CAST(Halfword*, prev_phased)[widx] = prev_phased_halfword; + } + } else { + // dosage (or {H}DS-force) and phase present, partly/fully + // haploid + uint32_t is_male_hw = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + if (is_x) { + is_male_hw = R_CAST(const Halfword*, sex_male_collapsed)[widx]; + } + uint32_t prev_phased_halfword = R_CAST(Halfword*, prev_phased)[widx]; + + if (pgv.phasepresent_ct) { + phasepresent_hw = R_CAST(Halfword*, pgv.phasepresent)[widx]; + } + + const uint32_t phaseinfo_hw = R_CAST(Halfword*, pgv.phaseinfo)[widx]; + if (pgv.dosage_ct) { + dosage_present_hw = R_CAST(Halfword*, pgv.dosage_present)[widx]; + if (pgv.dphase_ct) { + dphase_present_hw = R_CAST(Halfword*, pgv.dphase_present)[widx]; + } + } + uint32_t cur_shift = 1; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + const uint32_t cur_is_male = is_male_hw & 1; + const uint32_t cur_blen = haploid_genotext_blen[cur_geno + cur_is_male * 4]; + memcpy(write_iter, &(basic_genotext[cur_geno]), 4); + write_iter = &(write_iter[cur_blen]); + if (cur_blen == 4) { + // render current hardcall as diploid (chrX nonmale, or het + // haploid) + if (cur_geno == 1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + memcpy(&(write_iter[-4]), "\t1|0", 4); + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } + if (prev_phased_halfword & cur_shift) { + write_iter[-2] = '|'; + } + if (dosage_present_hw & cur_shift) { + *write_iter++ = ':'; + const uint32_t dosage_int = *dosage_main_iter++; + write_iter = PrintDiploidVcfDosage(dosage_int, write_ds, write_iter); + if (write_hds) { + if ((phasepresent_hw | dphase_present_hw) & cur_shift) { + int32_t cur_dphase_delta; + if (dphase_present_hw & cur_shift) { + cur_dphase_delta = *dphase_delta_iter++; + } else { + cur_dphase_delta = DosageHomdist(dosage_int); + if (!(phaseinfo_hw & cur_shift)) { + cur_dphase_delta = -cur_dphase_delta; + } + } + *write_iter++ = ':'; + write_iter = PrintHdsPair(dosage_int, cur_dphase_delta, write_iter); + } else if (hds_force) { + *write_iter++ = ':'; + char* write_iter2 = PrintHaploidNonintDosage(dosage_int, write_iter); + // do not render phased-dosage as diploid in unphased + // het haploid case + if (is_x && (!cur_is_male)) { + write_iter2[0] = ','; + write_iter2 = memcpya(&(write_iter2[1]), write_iter, write_iter2 - write_iter); + } + write_iter = write_iter2; + } + } + } else if (ds_force) { + write_iter = memcpya_k(write_iter, &(ds_inttext[cur_geno]), 2); + if (hds_force) { + uint32_t hds_inttext_index = cur_geno; + uint32_t tmp_blen; + if (phasepresent_hw & cur_shift) { + // do we want to remove this branch? doubt it's + // worthwhile since variable-length memcpy will + // branch anyway... + hds_inttext_index = 4 + ((phaseinfo_hw >> sample_idx_lowbits) & 1); + tmp_blen = 4; + } else { + // do not render phased-dosage as diploid in het + // haploid case + tmp_blen = hds_inttext_blen[cur_is_male * 4 + cur_geno]; + } + memcpy(write_iter, &(hds_inttext[hds_inttext_index]), 8); + write_iter = &(write_iter[tmp_blen]); + } + } + } else { + // render current hardcall as haploid + // (can't get here for hardcall-phased) + if (dosage_present_hw & cur_shift) { + *write_iter++ = ':'; + const uint32_t dosage_int = *dosage_main_iter++; + if (write_ds) { + write_iter = PrintHaploidNonintDosage(dosage_int, write_iter); + if (dphase_present_hw & cur_shift) { + // explicit dosage-phase, so render HDS as diploid + const int32_t cur_dphase_delta = *dphase_delta_iter++; + *write_iter++ = ':'; + write_iter = PrintHdsPair(dosage_int, cur_dphase_delta, write_iter); + } else if (hds_force) { + *write_iter++ = ':'; + write_iter = PrintHaploidNonintDosage(dosage_int, write_iter); + } + } else { + // GP + write_iter = PrintHaploidNonintDosage(kDosageMax - dosage_int, write_iter); + *write_iter++ = ','; + write_iter = PrintHaploidNonintDosage(dosage_int, write_iter); + } + } else if (ds_force) { + write_iter = memcpya_k(write_iter, &(ds_inttext[cur_geno + 4]), 2); + if (hds_force) { + memcpy(write_iter, &(hds_inttext[cur_geno]), 8); + write_iter = &(write_iter[hds_inttext_blen[cur_geno + 4]]); + } + } + } + genovec_word >>= 2; + is_male_hw >>= 1; + cur_shift <<= 1; + } + R_CAST(Halfword*, prev_phased)[widx] = prev_phased_halfword; + } + } + } + } + } else { + // multiallelic cases + // multiallelic dosage not supported yet + if (ref_allele_idx || (alt1_allele_idx != 1)) { + // todo: rotation function that can also be used by --make-pgen + // maybe add a PgrGetM2() function too + logputs("\n"); + logerrputs("Error: VCF-export multiallelic rotation is under development.\n"); + reterr = kPglRetNotYetSupported; + goto ExportVcf_ret_1; + } + if (!some_phased) { + reterr = PgrGetM(sample_include, pssi, sample_ct, variant_uidx, simple_pgrp, &pgv); + if (unlikely(reterr)) { + goto ExportVcf_ret_PGR_FAIL; + } + if (!ds_force) { + if (!is_haploid) { + if (allele_ct <= 10) { + GenoarrLookup256x4bx4(pgv.genovec, basic_genotext4, sample_ct, write_iter); + if (pgv.patch_01_ct) { + // Patch some 0/1 entries to 0/x. + char* genotext_offset3 = &(write_iter[3]); + uintptr_t sample_idx_base = 0; + uintptr_t patch_01_bits = pgv.patch_01_set[0]; + for (uint32_t uii = 0; uii != pgv.patch_01_ct; ++uii) { + const uintptr_t sample_idx = BitIter1(pgv.patch_01_set, &sample_idx_base, &patch_01_bits); + genotext_offset3[4 * sample_idx] = '0' + pgv.patch_01_vals[uii]; + } + } + if (pgv.patch_10_ct) { + // Patch some 1/1 entries to x/y. + char* genotext_offset1 = &(write_iter[1]); + uintptr_t sample_idx_base = 0; + uintptr_t patch_10_bits = pgv.patch_10_set[0]; + for (uint32_t uii = 0; uii != pgv.patch_10_ct; ++uii) { + const uintptr_t sample_idx = BitIter1(pgv.patch_10_set, &sample_idx_base, &patch_10_bits); + genotext_offset1[4 * sample_idx] = '0' + pgv.patch_10_vals[2 * uii]; + genotext_offset1[4 * sample_idx + 2] = '0' + pgv.patch_10_vals[2 * uii + 1]; + } + } + write_iter = &(write_iter[sample_ct * 4]); + } else { + if (!pgv.patch_01_ct) { + ZeroWArr(sample_ctl, pgv.patch_01_set); + } + if (!pgv.patch_10_ct) { + ZeroWArr(sample_ctl, pgv.patch_10_set); + } + const AlleleCode* patch_01_vals_iter = pgv.patch_01_vals; + const AlleleCode* patch_10_vals_iter = pgv.patch_10_vals; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + uint32_t multiallelic_hw = (R_CAST(const Halfword*, pgv.patch_01_set)[widx]) | (R_CAST(const Halfword*, pgv.patch_10_set)[widx]); + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + if (!(multiallelic_hw & 1)) { + write_iter = memcpya(write_iter, &(basic_genotext[cur_geno]), 4); + } else if (cur_geno == 1) { + write_iter = strcpya_k(write_iter, "\t0/"); + const AlleleCode ac = *patch_01_vals_iter++; + write_iter = u32toa(ac, write_iter); + } else { + AlleleCode ac = *patch_10_vals_iter++; + *write_iter++ = '\t'; + write_iter = u32toa_x(ac, '/', write_iter); + ac = *patch_10_vals_iter++; + write_iter = u32toa(ac, write_iter); + } + genovec_word >>= 2; + multiallelic_hw >>= 1; + } + } + } + } else { + if (!pgv.patch_01_ct) { + ZeroWArr(sample_ctl, pgv.patch_01_set); + } + if (!pgv.patch_10_ct) { + ZeroWArr(sample_ctl, pgv.patch_10_set); + } + // at least partially haploid, !ds_force + // We don't separately the allele_ct <= 10 vs. > 10 cases when + // entries are already variable-width in the <= 10 case. + const AlleleCode* patch_01_vals_iter = pgv.patch_01_vals; + const AlleleCode* patch_10_vals_iter = pgv.patch_10_vals; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + uint32_t sex_male_hw = is_x * (R_CAST(const Halfword*, sex_male_collapsed)[widx]); + // no need to separate patch_01 and patch_10 since this + // information is redundant with genovec_word contents + uint32_t multiallelic_hw = (R_CAST(const Halfword*, pgv.patch_01_set)[widx]) | (R_CAST(const Halfword*, pgv.patch_10_set)[widx]); + // probable todo: multiallelic_hw == 0 fast path, check + // whether <= inner_loop_last vs. < inner_loop_end makes a + // difference, etc. + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + const uint32_t cur_is_male = sex_male_hw & 1; + if (!(multiallelic_hw & 1)) { + memcpy(write_iter, &(basic_genotext[cur_geno]), 4); + write_iter = &(write_iter[haploid_genotext_blen[cur_geno + cur_is_male * 4]]); + } else if (cur_geno == 1) { + // always heterozygous, 4 characters + write_iter = strcpya_k(write_iter, "\t0/"); + const AlleleCode ac = *patch_01_vals_iter++; + write_iter = u32toa(ac, write_iter); + } else { + const AlleleCode ac0 = *patch_10_vals_iter++; + const AlleleCode ac1 = *patch_10_vals_iter++; + *write_iter++ = '\t'; + write_iter = u32toa(ac0, write_iter); + if ((ac0 != ac1) || (haploid_genotext_blen[2 + cur_is_male * 4] == 4)) { + *write_iter++ = '/'; + write_iter = u32toa(ac1, write_iter); + } + } + genovec_word >>= 2; + sex_male_hw >>= 1; + multiallelic_hw >>= 1; + } + } + } + } else { + if (!pgv.patch_01_ct) { + ZeroWArr(sample_ctl, pgv.patch_01_set); + } + if (!pgv.patch_10_ct) { + ZeroWArr(sample_ctl, pgv.patch_10_set); + } + // ds_force, !some_phased + write_iter = strcpya_k(write_iter, ":DS"); + if (hds_force) { + write_iter = strcpya_k(write_iter, ":HDS"); + } + const uint32_t allele_ct_m2 = allele_ct - 2; + const AlleleCode* patch_01_vals_iter = pgv.patch_01_vals; + const AlleleCode* patch_10_vals_iter = pgv.patch_10_vals; + if (!is_haploid) { + // DS-force, autosomal diploid, unphased, dosage_ct == 0 + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + uint32_t multiallelic_hw = (R_CAST(const Halfword*, pgv.patch_01_set)[widx]) | (R_CAST(const Halfword*, pgv.patch_10_set)[widx]); + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + if (!(multiallelic_hw & 1)) { + write_iter = memcpya(write_iter, &(basic_genotext[cur_geno]), 4); + // DS + write_iter = memcpya_k(write_iter, &(ds_inttext[cur_geno]), 2); + if (cur_geno != 3) { + // repeat ",0" if nonmissing + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + if (hds_force) { + const uint64_t cur_inttext = hds_inttext[cur_geno]; + const uint32_t hap_blen = hds_inttext_blen[cur_geno + 4]; + *R_CAST(uint32_t*, write_iter) = cur_inttext; + write_iter = &(write_iter[hap_blen]); + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + *R_CAST(uint32_t*, write_iter) = cur_inttext >> (8 * hap_blen); + write_iter = &(write_iter[hap_blen]); + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + } + } else { + if (hds_force) { + write_iter = strcpya_k(write_iter, ":.,."); + } + } + } else if (cur_geno == 1) { + const AlleleCode ac = *patch_01_vals_iter++; + write_iter = AppendVcfMultiallelicDsForce01(allele_ct_m2, hds_force, ac, 0, write_iter); + } else { + const AlleleCode ac0 = *patch_10_vals_iter++; + const AlleleCode ac1 = *patch_10_vals_iter++; + if (ac0 != ac1) { + write_iter = AppendVcfMultiallelicDsForce10Het(allele_ct_m2, hds_force, ac0, ac1, 0, write_iter); + } else { + write_iter = AppendVcfMultiallelicDsForce10HomDiploid(allele_ct_m2, hds_force, ac0, '/', write_iter); + } + } + genovec_word >>= 2; + multiallelic_hw >>= 1; + } + } + } else { + // DS-force, at least partly haploid, unphased + uint32_t sex_male_hw = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + if (is_x) { + sex_male_hw = R_CAST(const Halfword*, sex_male_collapsed)[widx]; + } + uint32_t multiallelic_hw = (R_CAST(const Halfword*, pgv.patch_01_set)[widx]) | (R_CAST(const Halfword*, pgv.patch_10_set)[widx]); + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + const uint32_t cur_is_male = sex_male_hw & 1; + const uint32_t should_be_diploid = is_x && (!cur_is_male); + if (!(multiallelic_hw & 1)) { + const uint32_t cur_genotext_blen = haploid_genotext_blen[cur_geno + cur_is_male * 4]; + memcpy(write_iter, &(basic_genotext[cur_geno]), 4); + write_iter = &(write_iter[cur_genotext_blen]); + // DS + write_iter = memcpya(write_iter, &(ds_inttext[cur_geno + 8 - 2 * cur_genotext_blen]), 2); + if (cur_geno != 3) { + // repeat ",0" + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + if (hds_force) { + const uint64_t cur_inttext = hds_inttext[cur_geno]; + const uint32_t hap_blen = hds_inttext_blen[4 + cur_geno]; + *R_CAST(uint32_t*, write_iter) = cur_inttext; + write_iter = &(write_iter[hap_blen]); + if (should_be_diploid) { + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + *R_CAST(uint32_t*, write_iter) = cur_inttext >> (8 * hap_blen); + write_iter = &(write_iter[hap_blen]); + } + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + } + } else { + if (hds_force) { + strcpy_k(write_iter, ":.,."); + write_iter = &(write_iter[2 + 2 * (is_x & (~cur_is_male))]); + } + } + } else if (cur_geno == 1) { + const AlleleCode ac = *patch_01_vals_iter++; + write_iter = AppendVcfMultiallelicDsForce01(allele_ct_m2, hds_force, ac, !should_be_diploid, write_iter); + } else { + const AlleleCode ac0 = *patch_10_vals_iter++; + const AlleleCode ac1 = *patch_10_vals_iter++; + if (ac0 != ac1) { + write_iter = AppendVcfMultiallelicDsForce10Het(allele_ct_m2, hds_force, ac0, ac1, !should_be_diploid, write_iter); + } else if (haploid_genotext_blen[cur_geno + cur_is_male * 4] == 2) { + write_iter = AppendVcfMultiallelicDsForce10Haploid(allele_ct_m2, hds_force, ac0, write_iter); + } else { + write_iter = AppendVcfMultiallelicDsForce10HomDiploid(allele_ct_m2, hds_force, ac0, '/', write_iter); + } + } + genovec_word >>= 2; + multiallelic_hw >>= 1; + } + } + } + } + } else { + // multiallelic, phased + reterr = PgrGetMP(sample_include, pssi, sample_ct, variant_uidx, simple_pgrp, &pgv); + if (unlikely(reterr)) { + goto ExportVcf_ret_PGR_FAIL; + } + if (!pgv.patch_01_ct) { + ZeroWArr(sample_ctl, pgv.patch_01_set); + } + if (!pgv.patch_10_ct) { + ZeroWArr(sample_ctl, pgv.patch_10_set); + } + const AlleleCode* patch_01_vals_iter = pgv.patch_01_vals; + const AlleleCode* patch_10_vals_iter = pgv.patch_10_vals; + uint32_t phasepresent_hw = 0; + if (!ds_force) { + if (!is_haploid) { + if (allele_ct <= 10) { + uint32_t* write_iter_u32_alias = R_CAST(uint32_t*, write_iter); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + const uint32_t multiallelic_hw = (R_CAST(const Halfword*, pgv.patch_01_set)[widx]) | (R_CAST(const Halfword*, pgv.patch_10_set)[widx]); + uint32_t prev_phased_halfword = R_CAST(Halfword*, prev_phased)[widx]; + + if (pgv.phasepresent_ct) { + phasepresent_hw = R_CAST(Halfword*, pgv.phasepresent)[widx]; + } + const uint32_t phaseinfo_hw = R_CAST(Halfword*, pgv.phaseinfo)[widx]; + uint32_t cur_shift = 1; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uintptr_t cur_geno = genovec_word & 3; + uint32_t cur_basic_genotext; + if (!(multiallelic_hw & cur_shift)) { + // usually "\t0/0", etc. + cur_basic_genotext = basic_genotext[cur_geno]; + if (cur_geno == 1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + cur_basic_genotext ^= 0x1000100; // 0|1 -> 1|0 + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } + } else { + AlleleCode ac0; + AlleleCode ac1; + if (cur_geno == 1) { + ac0 = 0; + ac1 = *patch_01_vals_iter++; + } else { + ac0 = *patch_10_vals_iter++; + ac1 = *patch_10_vals_iter++; + } + if (ac0 != ac1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + const AlleleCode ac_swap = ac0; + ac0 = ac1; + ac1 = ac_swap; + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } + cur_basic_genotext = 0x302f3009 + (ac0 * 256) + (ac1 * 0x1000000); + } + // '/' = ascii 47, '|' = ascii 124 + *write_iter_u32_alias++ = cur_basic_genotext + 0x4d0000 * ((prev_phased_halfword >> sample_idx_lowbits) & 1); + genovec_word >>= 2; + cur_shift = cur_shift * 2; + } + R_CAST(Halfword*, prev_phased)[widx] = prev_phased_halfword; + } + write_iter = R_CAST(char*, write_iter_u32_alias); + } else { + // phased, allele_ct > 10, !is_haploid, !ds_force + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + const uint32_t multiallelic_hw = (R_CAST(const Halfword*, pgv.patch_01_set)[widx]) | (R_CAST(const Halfword*, pgv.patch_10_set)[widx]); + uint32_t prev_phased_halfword = R_CAST(Halfword*, prev_phased)[widx]; + + if (pgv.phasepresent_ct) { + phasepresent_hw = R_CAST(Halfword*, pgv.phasepresent)[widx]; + } + + const uint32_t phaseinfo_hw = R_CAST(Halfword*, pgv.phaseinfo)[widx]; + uint32_t cur_shift = 1; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + if (!(multiallelic_hw & cur_shift)) { + write_iter = memcpya(write_iter, &(basic_genotext[cur_geno]), 4); + if (cur_geno == 1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + memcpy(&(write_iter[-4]), "\t1|0", 4); + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } + if (prev_phased_halfword & cur_shift) { + write_iter[-2] = '|'; + } + } else { + AlleleCode ac0; + AlleleCode ac1; + if (cur_geno == 1) { + ac0 = 0; + ac1 = *patch_01_vals_iter++; + } else { + ac0 = *patch_10_vals_iter++; + ac1 = *patch_10_vals_iter++; + } + if (ac0 != ac1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + const AlleleCode ac_swap = ac0; + ac0 = ac1; + ac1 = ac_swap; + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } + *write_iter++ = '\t'; + write_iter = u32toa(ac0, write_iter); + if (prev_phased_halfword & cur_shift) { + *write_iter++ = '|'; + } else { + *write_iter++ = '/'; + } + write_iter = u32toa(ac1, write_iter); + } + genovec_word >>= 2; + cur_shift = cur_shift * 2; + } + R_CAST(Halfword*, prev_phased)[widx] = prev_phased_halfword; + } + } + } else { + // phased, at least partially haploid, !ds_force + // probable todo: merge this with biallelic code, do the same for + // other less-common biallelic-variable-width cases + uint32_t is_male_hw = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + if (is_x) { + is_male_hw = R_CAST(const Halfword*, sex_male_collapsed)[widx]; + } + const uint32_t multiallelic_hw = (R_CAST(const Halfword*, pgv.patch_01_set)[widx]) | (R_CAST(const Halfword*, pgv.patch_10_set)[widx]); + uint32_t prev_phased_halfword = R_CAST(Halfword*, prev_phased)[widx]; + + if (pgv.phasepresent_ct) { + phasepresent_hw = R_CAST(Halfword*, pgv.phasepresent)[widx]; + } + + const uint32_t phaseinfo_hw = R_CAST(Halfword*, pgv.phaseinfo)[widx]; + uint32_t cur_shift = 1; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + const uint32_t cur_is_male = is_male_hw & 1; + if (!(multiallelic_hw & cur_shift)) { + const uint32_t cur_blen = haploid_genotext_blen[cur_geno + cur_is_male * 4]; + memcpy(write_iter, &(basic_genotext[cur_geno]), 4); + write_iter = &(write_iter[cur_blen]); + if (cur_blen == 4) { + if (cur_geno == 1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + memcpy(&(write_iter[-4]), "\t1|0", 4); + } else { + write_iter[-2] = '|'; + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } else if ((prev_phased_halfword >> sample_idx_lowbits) & 1) { + write_iter[-2] = '|'; + } + } + } else { + AlleleCode ac0; + AlleleCode ac1; + if (cur_geno == 1) { + ac0 = 0; + ac1 = *patch_01_vals_iter++; + } else { + ac0 = *patch_10_vals_iter++; + ac1 = *patch_10_vals_iter++; + } + *write_iter++ = '\t'; + if ((ac0 != ac1) || (haploid_genotext_blen[2 + cur_is_male * 4] == 4)) { + if (ac0 != ac1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + const AlleleCode ac_swap = ac0; + ac0 = ac1; + ac1 = ac_swap; + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } + write_iter = u32toa(ac0, write_iter); + if (prev_phased_halfword & cur_shift) { + *write_iter++ = '|'; + } else { + *write_iter++ = '/'; + } + } + write_iter = u32toa(ac1, write_iter); + } + genovec_word >>= 2; + is_male_hw >>= 1; + cur_shift = cur_shift * 2; + } + R_CAST(Halfword*, prev_phased)[widx] = prev_phased_halfword; + } + } + } else { + // phased, ds_force + write_iter = strcpya_k(write_iter, ":DS"); + if (hds_force) { + write_iter = strcpya_k(write_iter, ":HDS"); + } + const uint32_t allele_ct_m2 = allele_ct - 2; + if (!is_haploid) { + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + const uint32_t multiallelic_hw = (R_CAST(const Halfword*, pgv.patch_01_set)[widx]) | (R_CAST(const Halfword*, pgv.patch_10_set)[widx]); + uint32_t prev_phased_halfword = R_CAST(Halfword*, prev_phased)[widx]; + + if (pgv.phasepresent_ct) { + phasepresent_hw = R_CAST(Halfword*, pgv.phasepresent)[widx]; + } + + const uint32_t phaseinfo_hw = R_CAST(Halfword*, pgv.phaseinfo)[widx]; + uint32_t cur_shift = 1; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + if (!(multiallelic_hw & cur_shift)) { + write_iter = memcpya(write_iter, &(basic_genotext[cur_geno]), 4); + if (cur_geno == 1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + memcpy(&(write_iter[-4]), "\t1|0", 4); + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } + if (prev_phased_halfword & cur_shift) { + write_iter[-2] = '|'; + } + // DS + write_iter = memcpya_k(write_iter, &(ds_inttext[cur_geno]), 2); + if (cur_geno != 3) { + // repeat ",0" if nonmissing + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + if (hds_force) { + uint32_t hds_inttext_index = cur_geno; + if (phasepresent_hw & cur_shift) { + hds_inttext_index = 4 + ((phaseinfo_hw >> sample_idx_lowbits) & 1); + } + const uint64_t cur_inttext = hds_inttext[hds_inttext_index]; + const uint32_t hap_blen = hds_inttext_blen[hds_inttext_index] / 2; + *R_CAST(uint32_t*, write_iter) = cur_inttext; + write_iter = &(write_iter[hap_blen]); + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + *R_CAST(uint32_t*, write_iter) = cur_inttext >> (8 * hap_blen); + write_iter = &(write_iter[hap_blen]); + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + } + } else { + if (hds_force) { + write_iter = strcpya_k(write_iter, ":.,."); + } + } + } else { + AlleleCode ac0; + AlleleCode ac1; + if (cur_geno == 1) { + ac0 = 0; + ac1 = *patch_01_vals_iter++; + } else { + ac0 = *patch_10_vals_iter++; + ac1 = *patch_10_vals_iter++; + } + if (ac0 != ac1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + write_iter = AppendVcfMultiallelicDsForcePhased(allele_ct_m2, hds_force, ac0, ac1, phaseinfo_hw & cur_shift, write_iter); + } else { + prev_phased_halfword &= ~cur_shift; + if (!ac0) { + write_iter = AppendVcfMultiallelicDsForce01(allele_ct_m2, hds_force, ac1, 0, write_iter); + } else { + write_iter = AppendVcfMultiallelicDsForce10Het(allele_ct_m2, hds_force, ac0, ac1, 0, write_iter); + } + } + } else { + write_iter = AppendVcfMultiallelicDsForce10HomDiploid(allele_ct_m2, hds_force, ac0, (prev_phased_halfword & cur_shift)? '|' : '/', write_iter); + } + } + genovec_word >>= 2; + cur_shift <<= 1; + } + R_CAST(Halfword*, prev_phased)[widx] = prev_phased_halfword; + } + } else { + // dosage (or {H}DS-force) and phase present, partly/fully + // haploid + uint32_t is_male_hw = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + inner_loop_last = (sample_ct - 1) % kBitsPerWordD2; + } + uintptr_t genovec_word = pgv.genovec[widx]; + if (is_x) { + is_male_hw = R_CAST(const Halfword*, sex_male_collapsed)[widx]; + } + const uint32_t multiallelic_hw = (R_CAST(const Halfword*, pgv.patch_01_set)[widx]) | (R_CAST(const Halfword*, pgv.patch_10_set)[widx]); + uint32_t prev_phased_halfword = R_CAST(Halfword*, prev_phased)[widx]; + + if (pgv.phasepresent_ct) { + phasepresent_hw = R_CAST(Halfword*, pgv.phasepresent)[widx]; + } + + const uint32_t phaseinfo_hw = R_CAST(Halfword*, pgv.phaseinfo)[widx]; + uint32_t cur_shift = 1; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits <= inner_loop_last; ++sample_idx_lowbits) { + const uint32_t cur_geno = genovec_word & 3; + const uint32_t cur_is_male = is_male_hw & 1; + const uint32_t should_be_diploid = is_x && (!cur_is_male); + // bugfix (29 Dec 2018): need to check correct bit here + if (!(multiallelic_hw & cur_shift)) { + const uint32_t cur_blen = haploid_genotext_blen[cur_geno + cur_is_male * 4]; + memcpy(write_iter, &(basic_genotext[cur_geno]), 4); + write_iter = &(write_iter[cur_blen]); + if (cur_blen == 4) { + // render current hardcall as diploid (chrX nonmale, or + // het haploid) + if (cur_geno == 1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + if (phaseinfo_hw & cur_shift) { + memcpy(&(write_iter[-4]), "\t1|0", 4); + } + } else { + prev_phased_halfword &= ~cur_shift; + } + } + if (prev_phased_halfword & cur_shift) { + write_iter[-2] = '|'; + } + // DS + write_iter = memcpya_k(write_iter, &(ds_inttext[cur_geno]), 2); + if (cur_geno != 3) { + // repeat ",0" + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + if (hds_force) { + uint32_t hds_inttext_index = cur_geno; + if (phasepresent_hw & cur_shift) { + hds_inttext_index = 4 + ((phaseinfo_hw >> sample_idx_lowbits) & 1); + } + const uint64_t cur_inttext = hds_inttext[hds_inttext_index]; + const uint32_t hap_blen = hds_inttext_blen[hds_inttext_index] / 2; + *R_CAST(uint32_t*, write_iter) = cur_inttext; + write_iter = &(write_iter[hap_blen]); + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + // Don't render unphased het haploid as diploid here + if (should_be_diploid || (phasepresent_hw & cur_shift)) { + *R_CAST(uint32_t*, write_iter) = cur_inttext >> (8 * hap_blen); + write_iter = &(write_iter[hap_blen]); + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + } + } + } else { + if (hds_force) { + strcpy_k(write_iter, ":.,."); + write_iter = &(write_iter[2 + 2 * should_be_diploid]); + } + } + } else { + // render current hardcall as haploid + // (can't get here for hardcall-phased) + write_iter = memcpya_k(write_iter, &(ds_inttext[cur_geno + 4]), 2); + if (cur_geno != 3) { + // repeat ",0" + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + if (hds_force) { + // don't need to perform generic copy from + // hds_inttext, since only possibilities are :0 and + // :1 + *R_CAST(uint16_t*, write_iter) = 0x303a + 128 * cur_geno; + write_iter = &(write_iter[2]); + write_iter = u16setsa(write_iter, 0x302c, allele_ct_m2); + } + } else { + if (hds_force) { + write_iter = strcpya_k(write_iter, ":."); + } + } + } + } else { + AlleleCode ac0; + AlleleCode ac1; + if (cur_geno == 1) { + ac0 = 0; + ac1 = *patch_01_vals_iter++; + } else { + ac0 = *patch_10_vals_iter++; + ac1 = *patch_10_vals_iter++; + } + if (ac0 != ac1) { + if (phasepresent_hw & cur_shift) { + prev_phased_halfword |= cur_shift; + write_iter = AppendVcfMultiallelicDsForcePhased(allele_ct_m2, hds_force, ac0, ac1, phaseinfo_hw & cur_shift, write_iter); + } else { + prev_phased_halfword &= ~cur_shift; + if (!ac0) { + write_iter = AppendVcfMultiallelicDsForce01(allele_ct_m2, hds_force, ac1, !should_be_diploid, write_iter); + } else { + write_iter = AppendVcfMultiallelicDsForce10Het(allele_ct_m2, hds_force, ac0, ac1, !should_be_diploid, write_iter); + } + } + } else { + if (should_be_diploid) { + write_iter = AppendVcfMultiallelicDsForce10HomDiploid(allele_ct_m2, hds_force, ac0, (prev_phased_halfword & cur_shift)? '|' : '/', write_iter); + } else { + write_iter = AppendVcfMultiallelicDsForce10Haploid(allele_ct_m2, hds_force, ac0, write_iter); + } + } + } + genovec_word >>= 2; + is_male_hw >>= 1; + cur_shift <<= 1; + } + R_CAST(Halfword*, prev_phased)[widx] = prev_phased_halfword; + } + } + } + } + } + AppendBinaryEoln(&write_iter); + if (unlikely(bgzfwrite_ck(writebuf_flush, &bgzf, &write_iter))) { + goto ExportVcf_ret_WRITE_FAIL; + } + if (variant_idx >= next_print_variant_idx) { + if (pct > 10) { + putc_unlocked('\b', stdout); + } + pct = (variant_idx * 100LLU) / variant_ct; + printf("\b\b%u%%", pct++); + fflush(stdout); + next_print_variant_idx = (pct * S_CAST(uint64_t, variant_ct)) / 100; + } + } + if (unlikely(bgzfclose_flush(writebuf_flush, write_iter, &bgzf, &reterr))) { + goto ExportVcf_ret_1; + } + if (pct > 10) { + putc_unlocked('\b', stdout); + } + fputs("\b\b", stdout); + logputs("done.\n"); + if (invalid_allele_code_seen) { + logerrputs("Warning: At least one VCF allele code violates the official specification;\nother tools may not accept the file. (Valid codes must either start with a\n'<', only contain characters in {A,C,G,T,N,a,c,g,t,n}, be an isolated '*', or\nrepresent a breakend.)\n"); + } + } + while (0) { + ExportVcf_ret_NOMEM: + reterr = kPglRetNomem; + break; + ExportVcf_ret_TSTREAM_FAIL: + TextStreamErrPrint(pvar_info_reload, &pvar_reload_txs); + break; + ExportVcf_ret_WRITE_FAIL: + reterr = kPglRetWriteFail; + break; + ExportVcf_ret_MALFORMED_INPUT: + reterr = kPglRetMalformedInput; + break; + ExportVcf_ret_INCONSISTENT_INPUT: + reterr = kPglRetMalformedInput; + break; + ExportVcf_ret_PGR_FAIL: + PgenErrPrintN(reterr); + break; + } + ExportVcf_ret_1: + CleanupTextStream2(pvar_info_reload, &pvar_reload_txs, &reterr); + CleanupBgzfCompressStream(&bgzf, &reterr); + BigstackReset(bigstack_mark); + return reterr; +} + +/* +PglErr ExportBcf(const uintptr_t* sample_include, const uint32_t* sample_include_cumulative_popcounts, const SampleIdInfo* siip, const uintptr_t* sex_male_collapsed, const uintptr_t* variant_include, const ChrInfo* cip, const uint32_t* variant_bps, const char* const* variant_ids, const uintptr_t* allele_idx_offsets, const char* const* allele_storage, const STD_ARRAY_PTR_DECL(AlleleCode, 2, refalt1_select), const uintptr_t* pvar_qual_present, const float* pvar_quals, const uintptr_t* pvar_filter_present, const uintptr_t* pvar_filter_npass, const char* const* pvar_filter_storage, const char* pvar_info_reload, uintptr_t xheader_blen, InfoFlags info_flags, uint32_t sample_ct, uint32_t raw_variant_ct, uint32_t variant_ct, uint32_t max_allele_slen, uint32_t max_filter_slen, uint32_t info_reload_slen, uint32_t max_thread_ct, ExportfFlags exportf_flags, VcfExportMode vcf_mode, IdpasteFlags exportf_id_paste, char exportf_id_delim, char* xheader, PgenFileInfo* pgfip, PgenReader* simple_pgrp, char* outname, char* outname_end) { + unsigned char* bigstack_mark = g_bigstack_base; + PglErr reterr = kPglRetSuccess; + TextStream pvar_reload_txs; + BgzfCompressStream bgzf; + PreinitTextStream(&pvar_reload_txs); + PreinitBgzfCompressStream(&bgzf); + { + { + snprintf(outname_end, kMaxOutfnameExtBlen, ".bcf"); + reterr = InitBgzfCompressStreamEx(outname, 0, kBgzfDefaultClvl, max_thread_ct, &bgzf); + if (unlikely(reterr)) { + if (reterr == kPglRetOpenFail) { + logerrprintfww(kErrprintfFopen, outname, strerror(errno)); + } + goto ExportBcf_ret_1; + } + } + const uint32_t max_chr_slen = GetMaxChrSlen(cip); + uintptr_t writebuf_blen = kMaxIdSlen + max_chr_slen + max_allele_slen; + uint32_t write_some_dosage = 0; + uint32_t write_ds = 0; + uint32_t write_hds = 0; + uint32_t ds_force = 0; + uint32_t hds_force = 0; + if (vcf_mode != kVcfExport0) { + write_some_dosage = 1; + if (vcf_mode != kVcfExportGp) { + write_ds = 1; + if (vcf_mode == kVcfExportDsForce) { + ds_force = 1; + } else if (vcf_mode != kVcfExportDs) { + write_hds = 1; + if (vcf_mode == kVcfExportHdsForce) { + ds_force = 1; + hds_force = 1; + } + } + } + } + if ((!ds_force) && write_some_dosage && (!(pgfip->gflags & kfPgenGlobalDosagePresent))) { + write_some_dosage = 0; + logerrprintf("Warning: No dosage data present. %s will not be exported.\n", write_hds? "DS and HDS fields" : (write_ds? "DS field" : "GP field")); + write_ds = 0; + write_hds = 0; + } + // max_allele_ct == 2: + // GT: 2 bytes + // GP: 3 floats = 12 bytes + // DS + HDS: 3 floats = 12 bytes + // DS only: 1 float = 4 bytes + // max_allele_ct > 2: + // GT: 2 bytes if max_allele_ct < 64, 4 bytes otherwise + // GP: if requested, check max_gp_allele_ct among multiallelic-dosage + // variants. + // if none found (always true for now), 12 bytes. + // if present, (max_gp_allele_ct * (max_gp_allele_ct + 1) / 2) * 8, + // which sucks ass + // DS + HDS: 3 * (max_allele_ct - 1) floats + // DS only: (max_allele_ct - 1) floats + uintptr_t output_bytes_per_sample; + if (!allele_idx_offsets) { + if (write_some_dosage) { + output_bytes_per_sample = (write_ds && (!write_hds))? 6 : 14; + } else { + output_bytes_per_sample = 2; + } + } else { + const uint32_t max_allele_ct = PgrGetMaxAlleleCt(simple_pgrp); + output_bytes_per_sample = 2 + 2 * (max_allele_ct > 63); + if (write_some_dosage) { + if (write_ds) { + if (write_hds) { + output_bytes_per_sample += 12 * (max_allele_ct - 1); + } else { + output_bytes_per_sample += 4 * (max_allele_ct - 1); + } + } else { + // GP should only be written for biallelic variants, for now + // would need to take max of this and GT length, but this is always + // larger + output_bytes_per_sample = 14; + } + } + } + { + // FILTER entries past the first always require 2+ .pvar bytes, and never + // require more than 4 output bytes. + // INFO worst cases are also limited to a 2:4 ratio. + const uintptr_t writebuf_blen_lbound2 = (max_filter_slen + info_reload_slen) * 2; + if (writebuf_blen < writebuf_blen_lbound1) { + writebuf_blen = writebuf_blen_lbound1; + } + const uintptr_t writebuf_blen_lbound2 = sample_ct * output_bytes_per_sample; + if (writebuf_blen < writebuf_blen_lbound2) { + writebuf_blen = writebuf_blen_lbound2; + } + } + writebuf_blen += kMaxMediumLine + 32; + // The byte size of the header must be stored in bytes [5,9). So we + // generate the entire header, fill in its size, and only then do we flush. + // Along the way, we need to ensure we don't write past the end of + // workspace memory. + uintptr_t header_ubound = xheader_blen + 512; + // TODO: reserve space for contig/FILTER/INFO/FORMAT IDX= tags + // might need additional bytes for ##contig lines + { + const uint32_t max_code = cip->max_code; + for (uint32_t chr_fo_idx = 0; chr_fo_idx != cip->chr_ct; ++chr_fo_idx) { + const uint32_t chr_idx = cip->chr_file_order[chr_fo_idx]; + if (!IsSet(cip->chr_mask, chr_idx)) { + continue; + } + // ##contig= + // 32 + strlen(EOLN_STR) + length of contig ID + header_ubound += 32 + strlen(EOLN_STR); + if (chr_idx <= max_code) { + // regular code + header_ubound += kMaxChrTextnumSlen + 3; + } else { + header_ubound += strlen(cip->nonstd_names[chr_idx]); + } + } + } + char* writebuf; + if (unlikely(bigstack_alloc_c(header_ubound, &writebuf))) { + goto ExportBcf_ret_NOMEM; + } + char* write_iter = memcpya_k(writebuf, "BCF\2\2\0\0\0", 9); + char* header_start = write_iter; + write_iter = strcpya_k(writebuf, "##fileformat=VCFv4."); + *write_iter++ = (exportf_flags & kfExportfBcf43)? '3' : '2'; + write_iter = strcpya_k(write_iter, EOLN_STR "##fileDate="); + time_t rawtime; + time(&rawtime); + struct tm* loctime; + loctime = localtime(&rawtime); + write_iter += strftime(write_iter, kMaxMediumLine, "%Y%m%d", loctime); + write_iter = strcpya_k(write_iter, EOLN_STR "##source=PLINKv2.00" EOLN_STR); + if (cip->chrset_source) { + AppendChrsetLine(cip, &write_iter); + } + const uint32_t chr_ctl = BitCtToWordCt(cip->chr_ct); + uintptr_t* written_contig_header_lines; + if (unlikely(bigstack_calloc_w(chr_ctl, &written_contig_header_lines))) { + goto ExportVcf_ret_NOMEM; + } + const uint32_t x_code = cip->xymt_codes[kChrOffsetX]; + const uint32_t par1_code = cip->xymt_codes[kChrOffsetPAR1]; + const uint32_t par2_code = cip->xymt_codes[kChrOffsetPAR2]; + uint32_t x_contig_line_written = 0; + if (xheader) { + write_iter = strcpya_k(write_iter, "##contig= 14) && StrStartsWithUnsafe(xheader_iter, "##contig='), it's + // useless anyway, throw it out + continue; + } + // if GetChrCodeCounted() is modified to not mutate + // contig_name_start[], xheader can be changed to const char* + const uint32_t chr_idx = GetChrCodeCounted(cip, contig_name_end - contig_name_start, contig_name_start); + if (IsI32Neg(chr_idx) || (!IsSet(cip->chr_mask, chr_idx)) || (chr_idx == par1_code) || (chr_idx == par2_code)) { + continue; + } + if (chr_idx == x_code) { + x_contig_line_written = 1; + } + const uint32_t chr_fo_idx = cip->chr_idx_to_foidx[chr_idx]; + if (unlikely(IsSet(written_contig_header_lines, chr_fo_idx))) { + logerrputs("Error: Duplicate ##contig line in .pvar file.\n"); + goto ExportBcf_ret_MALFORMED_INPUT; + } + SetBit(chr_fo_idx, written_contig_header_lines); + // if --output-chr was used at some point, we need to sync the + // ##contig chromosome code with the code in the BCF body. + write_iter = chrtoa(cip, chr_idx, write_iter); + write_iter = memcpya(write_iter, contig_name_end, line_end - contig_name_end); + } else { + write_iter = memcpya(write_iter, xheader_iter, slen); + } + } + } + // fill in the missing ##contig lines + uint32_t contig_zero_written = 0; + uint32_t chrx_end = 0; + for (uint32_t chr_fo_idx = 0; chr_fo_idx != cip->chr_ct; ++chr_fo_idx) { + if (IsSet(written_contig_header_lines, chr_fo_idx)) { + continue; + } + const uint32_t chr_idx = cip->chr_file_order[chr_fo_idx]; + if ((!IsSet(cip->chr_mask, chr_idx)) || AllBitsAreZero(variant_include, cip->chr_fo_vidx_start[chr_fo_idx], cip->chr_fo_vidx_start[chr_fo_idx + 1])) { + continue; + } + if ((chr_idx == x_code) || (chr_idx == par1_code) || (chr_idx == par2_code)) { + // if variant included, should add max allele length instead of 1 + const uint32_t pos_end = variant_bps[cip->chr_fo_vidx_start[chr_fo_idx + 1] - 1] + 1; + if (pos_end > chrx_end) { + chrx_end = pos_end; + } + continue; + } + char* chr_name_write_start = strcpya_k(write_iter, "##contig=" EOLN_STR); @@ -4379,8 +6284,6 @@ if (write_ds) { write_iter = strcpya_k(write_iter, "##FORMAT=" EOLN_STR); if (write_hds) { - // bugfix (3 May 2018): 'Number=2' was inaccurate for haploid calls. - // Note that HDS ploidy intentionally does NOT match GT ploidy in the // unphased het haploid case. write_iter = strcpya_k(write_iter, "##FORMAT=" EOLN_STR); @@ -4389,40 +6292,49 @@ write_iter = strcpya_k(write_iter, "##FORMAT=" EOLN_STR); } // possible todo: optionally export .psam information as - // PEDIGREE/META/SAMPLE lines in header, and make --vcf be able to read it + // PEDIGREE/META/SAMPLE lines in header, and make --vcf/--bcf be able to + // read it write_iter = strcpya_k(write_iter, "##FORMAT=" EOLN_STR "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\tFORMAT"); char* exported_sample_ids; uint32_t* exported_id_htable; uintptr_t max_exported_sample_id_blen; if (unlikely(ExportIdpaste(sample_include, siip, "vcf", sample_ct, exportf_id_paste, exportf_id_delim, &max_exported_sample_id_blen, &exported_sample_ids, &exported_id_htable))) { - goto ExportVcf_ret_NOMEM; + goto ExportBcf_ret_NOMEM; } for (uint32_t sample_idx = 0; sample_idx != sample_ct; ++sample_idx) { *write_iter++ = '\t'; write_iter = strcpya(write_iter, &(exported_sample_ids[sample_idx * max_exported_sample_id_blen])); - if (unlikely(bgzfwrite_ck(writebuf_flush, &bgzf, &write_iter))) { - goto ExportVcf_ret_WRITE_FAIL; - } } AppendBinaryEoln(&write_iter); - BigstackReset(exported_sample_ids); + *write_iter++ = '\0'; + assert(write_iter <= written_contig_header_lines); +#ifdef __LP64__ + if (unlikely(S_CAST(uintptr_t, write_iter - header_start) > 0xffffffffU)) { + logerrputs("Error: Cannot export BCF, since header would be too large.\n"); + goto ExportBcf_ret_INCONSISTENT_INPUT; + } +#endif + char* writebuf_flush = &(writebuf[kMaxMediumLine]); + if (unlikely(bgzfwrite_ck(writebuf_flush, &bgzf, &write_iter))) { + goto ExportBcf_ret_WRITE_FAIL; + } + BigstackReset(writebuf); + if (unlikely(bigstack_alloc_c(writebuf_blen, &writebuf))) { + goto ExportBcf_ret_NOMEM; + } - logprintfww5("--export vcf%s to %s ... ", (exportf_flags & kfExportfBgz)? " bgz" : "", outname); + logprintfww5("--export bcf to %s ... ", outname); fputs("0%", stdout); fflush(stdout); - // includes trailing tab - char* chr_buf; - const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); const uint32_t sample_ctl = BitCtToWordCt(sample_ct); PgenVariant pgv; if (unlikely( - bigstack_alloc_c(max_chr_blen, &chr_buf) || // if we weren't using bigstack_alloc, this would need to be // sample_ctaw2 bigstack_alloc_w(sample_ctl2, &pgv.genovec))) { - goto ExportVcf_ret_NOMEM; + goto ExportBcf_ret_NOMEM; } pgv.patch_01_set = nullptr; pgv.patch_01_vals = nullptr; @@ -4434,7 +6346,7 @@ bigstack_alloc_ac(sample_ct, &(pgv.patch_01_vals)) || bigstack_alloc_w(sample_ctl, &(pgv.patch_10_set)) || bigstack_alloc_ac(sample_ct * 2, &(pgv.patch_10_vals)))) { - goto ExportVcf_ret_NOMEM; + goto ExportBcf_ret_NOMEM; } } // For now, if phased data is present, each homozygous call is represented @@ -4484,7 +6396,7 @@ if (pvar_info_reload) { reterr = PvarInfoOpenAndReloadHeader(pvar_info_reload, 1 + (max_thread_ct > 1), &pvar_reload_txs, &pvar_reload_line_iter, &info_col_idx); if (unlikely(reterr)) { - goto ExportVcf_ret_TSTREAM_REWIND_FAIL; + goto ExportVcf_ret_TSTREAM_FAIL; } } @@ -4677,7 +6589,7 @@ if (pvar_reload_line_iter) { reterr = PvarInfoReloadAndWrite(info_pr_flag_present, info_col_idx, variant_uidx, is_pr, &pvar_reload_txs, &pvar_reload_line_iter, &write_iter, &rls_variant_uidx); if (unlikely(reterr)) { - goto ExportVcf_ret_TSTREAM_REWIND_FAIL; + goto ExportVcf_ret_TSTREAM_FAIL; } } else { if (is_pr) { @@ -5994,31 +7906,32 @@ } } while (0) { - ExportVcf_ret_NOMEM: + ExportBcf_ret_NOMEM: reterr = kPglRetNomem; break; - ExportVcf_ret_TSTREAM_REWIND_FAIL: - TextStreamErrPrintRewind(pvar_info_reload, &pvar_reload_txs, &reterr); + ExportBcf_ret_TSTREAM_FAIL: + TextStreamErrPrint(pvar_info_reload, &pvar_reload_txs); break; - ExportVcf_ret_WRITE_FAIL: + ExportBcf_ret_WRITE_FAIL: reterr = kPglRetWriteFail; break; - ExportVcf_ret_MALFORMED_INPUT: + ExportBcf_ret_MALFORMED_INPUT: reterr = kPglRetMalformedInput; break; - ExportVcf_ret_INCONSISTENT_INPUT: + ExportBcf_ret_INCONSISTENT_INPUT: reterr = kPglRetMalformedInput; break; - ExportVcf_ret_PGR_FAIL: + ExportBcf_ret_PGR_FAIL: PgenErrPrintN(reterr); break; } - ExportVcf_ret_1: + ExportBcf_ret_1: CleanupTextStream2(pvar_info_reload, &pvar_reload_txs, &reterr); CleanupBgzfCompressStream(&bgzf, &reterr); BigstackReset(bigstack_mark); return reterr; } +*/ static const Dosage kGenoToDosage[4] = {0, 16384, 32768, 65535}; @@ -6747,6 +8660,14 @@ goto Exportf_ret_1; } } + /* + if (flags & kfExportfBcf) { + reterr = ExportBcf(sample_include, sample_include_cumulative_popcounts, &(piip->sii), sex_male_collapsed, variant_include, cip, variant_bps, variant_ids, allele_idx_offsets, allele_storage, refalt1_select, pvar_qual_present, pvar_quals, pvar_filter_present, pvar_filter_npass, pvar_filter_storage, pvar_info_reload, xheader_blen, info_flags, sample_ct, raw_variant_ct, variant_ct, max_allele_slen, max_filter_slen, info_reload_slen, max_thread_ct, flags, eip->vcf_mode, idpaste_flags, id_delim, xheader, pgfip, simple_pgrp, outname, outname_end); + if (unlikely(reterr)) { + goto Exportf_ret_1; + } + } + */ // todo: everything else // sample-major output should share a (probably multithreaded) transpose // routine diff -Nru plink2-2.00~a3-200116+dfsg/plink2_filter.cc plink2-2.00~a3-200217+dfsg/plink2_filter.cc --- plink2-2.00~a3-200116+dfsg/plink2_filter.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_filter.cc 2020-02-09 00:58:43.000000000 +0000 @@ -722,7 +722,7 @@ const uint32_t decompress_thread_ct = ClipU32(max_thread_ct - 1, 1, 4); reterr = SizeAndInitTextStream(pvar_info_reload, bigstack_left() / 4, decompress_thread_ct, &pvar_txs); if (unlikely(reterr)) { - goto RmDup_ret_TSTREAM_REWIND_FAIL; + goto RmDup_ret_TSTREAM_FAIL; } logputs("--rm-dup: Loading INFO field... "); fflush(stdout); @@ -732,7 +732,7 @@ do { reterr = TextNextLineLstrip(&pvar_txs, &line_iter); if (unlikely(reterr)) { - goto RmDup_ret_TSTREAM_REWIND_FAIL; + goto RmDup_ret_TSTREAM_FAIL; } } while (!tokequal_k(line_iter, "#CHROM")); uint32_t info_col_idx = 1; @@ -742,7 +742,8 @@ char* token_start = FirstNonTspace(line_iter); if (IsEolnKns(*token_start)) { reterr = kPglRetRewindFail; - goto RmDup_ret_TSTREAM_REWIND_FAIL; + logerrprintfww(kErrprintfRewind, pvar_info_reload); + goto RmDup_ret_1; } line_iter = CurTokenEnd(token_start); if (strequal_k(token_start, "INFO", line_iter - token_start)) { @@ -755,19 +756,16 @@ for (uint32_t variant_uidx = 0; ; ++variant_uidx) { reterr = TextNextLineLstrip(&pvar_txs, &line_iter); if (unlikely(reterr)) { - goto RmDup_ret_TSTREAM_REWIND_FAIL; + goto RmDup_ret_TSTREAM_FAIL; } if (IsSet(orig_dups, variant_uidx)) { if (!memequal(variant_ids[variant_uidx], missing_varid_match, missing_varid_blen)) { char* info_start = NextTokenMult(line_iter, info_col_idx); line_iter = CurTokenEnd(info_start); const uint32_t info_slen = line_iter - info_start; - tmp_alloc_end -= info_slen + 1; - if (unlikely(tmp_alloc_end < tmp_alloc_base)) { + if (StoreStringAtEndK(tmp_alloc_base, info_start, info_slen, &tmp_alloc_end, &(dup_info_strs[dup_variant_idx]))) { goto RmDup_ret_NOMEM; } - memcpyx(tmp_alloc_end, info_start, info_slen, '\0'); - dup_info_strs[dup_variant_idx] = R_CAST(char*, tmp_alloc_end); } ++dup_variant_idx; if (dup_variant_idx == orig_dup_ct) { @@ -1124,8 +1122,8 @@ RmDup_ret_PGR_FAIL: PgenErrPrintN(reterr); break; - RmDup_ret_TSTREAM_REWIND_FAIL: - TextStreamErrPrintRewind(pvar_info_reload, &pvar_txs, &reterr); + RmDup_ret_TSTREAM_FAIL: + TextStreamErrPrint(pvar_info_reload, &pvar_txs); break; RmDup_ret_WRITE_FAIL: reterr = kPglRetWriteFail; diff -Nru plink2-2.00~a3-200116+dfsg/plink2_help.cc plink2-2.00~a3-200217+dfsg/plink2_help.cc --- plink2-2.00~a3-200116+dfsg/plink2_help.cc 2020-01-17 05:16:09.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_help.cc 2020-02-18 00:23:10.000000000 +0000 @@ -99,7 +99,7 @@ " exclusive options.\n" " * [square brackets without quotes or braces] denote an optional parameter,\n" " where the text between the brackets describes its nature.\n" -" * An ellipsis (...) indicates that you may enter multiple parameters of the\n" +" * An ellipsis (...) indicates that you may enter multiple arguments of the\n" " specified type.\n" " * A \"column set descriptor\" is either\n" " 1. a comma-separated sequence of column set names; this is interpreted as\n" @@ -143,7 +143,7 @@ // probable todo: dosage=AD mode. HelpPrint("vcf\0bcf\0psam\0fam\0", &help_ctrl, 1, " --vcf ['dosage=']\n" -" --bcf ['dosage='] (not implemented yet) :\n" +" --bcf ['dosage='] :\n" " Specify full name of .vcf{|.gz|.zst} or BCF2 file to import.\n" " * These can be used with --psam/--fam.\n" " * By default, dosage information is not imported. To import the GP field\n" @@ -234,7 +234,7 @@ " This generates a fake input dataset with the specified number of samples\n" " and SNPs.\n" " * By default, the missing dosage and phenotype frequencies are zero.\n" -" These can be changed by providing 3rd and 4th numeric parameters.\n" +" These can be changed by providing 3rd and 4th numeric arguments.\n" " * By default, allele codes are As and Bs; this can be changed with the\n" " 'acgt', '1234', or '12' modifier.\n" " * By default, one binary phenotype is generated. 'pheno-ct=' can be used\n" @@ -465,26 +465,25 @@ " * 'structure': Structure-format.\n" " * 'transpose': PLINK 1 variant-major (.tped + .tfam), loadable with\n" " --tfile.\n" -" * 'vcf', 'vcf-4.2': VCF (default version 4.3). If PAR1 and PAR2 are\n" -" present, they are automatically merged with chrX, with\n" -" proper handling of chromosome codes and male ploidy.\n" -" When the 'bgz' modifier is present, the VCF file is\n" -" block-gzipped.\n" -" The 'id-paste' modifier controls which .psam columns\n" -" are used to construct sample IDs (choices are maybefid,\n" -" fid, iid, maybesid, and sid; default is\n" -" maybefid,iid,maybesid), while the 'id-delim' modifier\n" -" sets the character between the ID pieces (default '_').\n" -" Dosages are not exported unless the 'vcf-dosage='\n" -" modifier is present. The following five dosage export\n" -" modes are supported:\n" -" 'GP': genotype posterior probabilities (v4.3 only).\n" -" 'DS': Minimac3-style dosages, omitted for hardcalls.\n" -" 'DS-force': Minimac3-style dosages, never omit.\n" -" 'HDS': Minimac3-style phased dosages, omitted for\n" -" hardcalls and unphased calls. Also includes\n" -" 'DS' output.\n" -" 'HDS-force': Always report DS and HDS.\n" +" * 'vcf', : VCF (default version 4.3). If PAR1 and PAR2 are present,\n" +" 'vcf-4.2' they are automatically merged with chrX, with proper\n" +" handling of chromosome codes and male ploidy.\n" +" When the 'bgz' modifier is present, the VCF file is\n" +" block-gzipped.\n" +" The 'id-paste' modifier controls which .psam columns are\n" +" used to construct sample IDs (choices are maybefid, fid,\n" +" iid, maybesid, and sid; default is maybefid,iid,maybesid),\n" +" while the 'id-delim' modifier sets the character between the\n" +" ID pieces (default '_').\n" +" Dosages are not exported unless the 'vcf-dosage=' modifier\n" +" is present. The following five dosage export modes are\n" +" supported:\n" +" 'GP': genotype posterior probabilities (v4.3 only).\n" +" 'DS': Minimac3-style dosages, omitted for hardcalls.\n" +" 'DS-force': Minimac3-style dosages, never omit.\n" +" 'HDS': Minimac3-style phased dosages, omitted for hardcalls\n" +" and unphased calls. Also includes 'DS' output.\n" +" 'HDS-force': Always report DS and HDS.\n" // possible todo: pedigree output? " In addition,\n" " * The '12' modifier causes alt1 alleles to be coded as '1' and ref alleles\n" @@ -899,7 +898,7 @@ " * It is usually best to perform this calculation on a variant set in\n" " approximate linkage equilibrium, with no very-low-MAF variants.\n" " * By default, 10 PCs are extracted; you can adjust this by passing a\n" -" numeric parameter. (Note that 10 is lower than the PLINK 1.9 default of\n" +" numeric argument. (Note that 10 is lower than the PLINK 1.9 default of\n" " 20; this is due to the randomized algorithm's memory footprint growing\n" " quadratically w.r.t. the PC count.)\n" " * The 'approx' modifier causes the standard deterministic computation to be\n" @@ -1159,8 +1158,9 @@ " scoreavgs: Score averages.\n" " scoresums: Score sums.\n" " The default is maybefid,maybesid,phenos,nallele,dosagesum,scoreavgs.\n" -" For more sophisticated polygenic risk scoring, we recommend the PRSice-2\n" -" software package (https://www.prsice.info/ ).\n\n" +" For more sophisticated polygenic risk scoring, we recommend looking at the\n" +" LDpred (https://github.com/bvilhjal/ldpred ) and PRSice-2\n" +" (https://www.prsice.info/ ) software packages.\n\n" ); HelpPrint("variant-score\0vscore\0", &help_ctrl, 1, " --variant-score ['zs'] ['bin' | 'cols=']\n" @@ -1185,7 +1185,7 @@ " (Variant scores are always present, and positioned here.)\n" " Default is chrom,pos,ref,alt.\n" " If binary output is requested instead, the main .vscore.bin matrix contains\n" -" double-precision floating-point values, column (score) ID(s) are saved to a\n" +" double-precision floating-point values, column (score) ID(s) are saved to\n" " .vscore.cols, and variant IDs are saved to\n" " .vscore.vars[.zst].\n\n" ); @@ -1316,7 +1316,7 @@ " (where the x_i's are 0..2 allele dosages),\n" " is not greater than 0.1. You can adjust\n" " this threshold by providing a numeric\n" -" parameter to --hard-call-threshold.\n" +" argument to --hard-call-threshold.\n" " You can also use this with --make-[b]pgen\n" " to alter the saved hardcalls while leaving\n" " the dosages untouched, or --make-bed to\n" @@ -1367,7 +1367,7 @@ " --chr-override ['file'] : By default, if --chr-set/--autosome-num/--cow/etc.\n" " conflicts with an input file ##chrSet header line,\n" " PLINK 2 will error out. --chr-override with no\n" -" parameter causes the command line to take\n" +" argument causes the command line to take\n" " precedence; '--chr-override file' defers to the\n" " file.\n" ); @@ -1504,6 +1504,10 @@ " the union, of the files to\n" " remain.\n" ); + HelpPrint("bed-border-bp\0bed-border-kb\0make-set-border\0extract\0exclude\0extract-intersect\0range\0", &help_ctrl, 0, +" --bed-border-bp : Stretch BED intervals by the given amount on each\n" +" --bed-border-kb side.\n" + ); HelpPrint("keep-cats\0keep-cat-names\0keep-cat-pheno\0remove-cats\0remove-cat-names\0remove-cat-pheno\0keep-clusters\0keep-cluster-names\0remove-clusters\0remove-cluster-names\0", &help_ctrl, 0, " --keep-cats : These can be used individually or in combination\n" " --keep-cat-names to define a list of categories to keep; all\n" @@ -1551,7 +1555,7 @@ " --variance-standardize [pheno/covar name(s)...]\n" " --covar-variance-standardize [covar name(s)...] :\n" " Linearly transform named covariates (and quantitative phenotypes, if\n" -" --variance-standardize) to mean-zero, variance 1. If no parameters are\n" +" --variance-standardize) to mean-zero, variance 1. If no arguments are\n" " provided, all possible phenotypes/covariates are affected.\n" " This is frequently necessary to prevent multicollinearity when dealing with\n" " covariates where abs(mean) is much larger than abs(standard deviation),\n" @@ -1640,7 +1644,7 @@ " --mind [val] [{dosage | hh-missing}] : \n" " Exclude variants (--geno) and/or samples (--mind) with missing call\n" " frequencies greater than a threshold (default 0.1). (Note that the default\n" -" threshold is only applied if --geno/--mind is invoked without a parameter;\n" +" threshold is only applied if --geno/--mind is invoked without an argument;\n" " when --geno/--mind is not invoked, no missing call frequency ceiling is\n"" enforced at all. Other inclusion/exclusion default thresholds work the\n" " same way.)\n" " By default, when a dosage is present but a hardcall is not, the genotype is\n" @@ -1660,7 +1664,7 @@ */ HelpPrint("require-pheno\0require-covar\0keep-if\0remove-if\0prune\0", &help_ctrl, 0, " --require-pheno [name(s)...] : Remove samples missing any of the named\n" -" --require-covar [name(s)...] phenotype(s)/covariate(s). If no parameters\n" +" --require-covar [name(s)...] phenotype(s)/covariate(s). If no arguments\n" " are provided, all phenotype(s)/covariate(s)\n" " must be present.\n" ); @@ -1722,7 +1726,7 @@ " variants, with r2 = nan, are not excluded.)\n" " * This is NOT identical to the R2 metric\n" " reported by Minimac3 0.1.13+; see below.\n" -" * If a single parameter is provided, it is\n" +" * If a single argument is provided, it is\n" " treated as the minimum.\n" " * The metric is not computed on chrX and MT.\n" " --minimac3-r2-filter [max] : Compute Minimac3 R2 values from scratch,\n" @@ -1948,7 +1952,7 @@ " * 'ascii'/'a' sorts in ASCII order, e.g.\n" " 'ID3' < 'id10' < 'id2'.\n" " * 'file'/'f' uses the order in the given file\n" -" (named in the last parameter).\n" +" (named in the last argument).\n" ); // todo: add citation for 2018 KING update paper, which should discuss the // two-stage screen + refine workflow supported by --king-table-subset, @@ -1958,7 +1962,7 @@ " inclusion in --make-king-table report.\n" " --king-table-subset [kmin] : Restrict current --make-king-table run to\n" " sample pairs listed in the given .kin0 file.\n" -" If a second parameter is provided, only\n" +" If a second argument is provided, only\n" " sample pairs with kinship >= that threshold\n" " (in the input .kin0) are processed.\n" ); @@ -2015,7 +2019,7 @@ ); HelpPrint("adjust-chr-field\0adjust-pos-field\0adjust-id-field\0adjust-ref-field\0adjust-alt-field\0adjust-a1-field\0adjust-test-field\0adjust-p-field\0adjust-file\0", &help_ctrl, 0, " --adjust-chr-field : Set --adjust-file input field names. When\n" -" --adjust-pos-field multiple parameters are given to these flags,\n" +" --adjust-pos-field multiple arguments are given to these flags,\n" " --adjust-id-field earlier names take precedence over later ones.\n" " --adjust-ref-field \n" " --adjust-alt-field \n" diff -Nru plink2-2.00~a3-200116+dfsg/plink2_import.cc plink2-2.00~a3-200217+dfsg/plink2_import.cc --- plink2-2.00~a3-200116+dfsg/plink2_import.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_import.cc 2020-02-18 05:39:37.000000000 +0000 @@ -84,6 +84,15 @@ uintptr_t line_idx; // for error reporting } GparseReadVcfMetadata; +typedef struct GparseReadBcfMetadataStruct { + // [0] = GQ, [1] = DP + STD_ARRAY_DECL(uint32_t, 2, qual_vec_offsets); + uint32_t gt_vec_offset; + uint32_t dosage_vec_offset; + uint32_t hds_vec_offset; + uintptr_t rec_idx; // for error reporting +} GparseReadBcfMetadata; + // The "parsed form" is as follows (all arrays vector-aligned, vector counts in // parentheses): // genovec (sample_ctv2) @@ -148,6 +157,7 @@ union GparseMetadata { GparseReadVcfMetadata read_vcf; + GparseReadBcfMetadata read_bcf; GparseReadBgenMetadata read_bgen; GparseWriteMetadata write; }; @@ -869,13 +879,14 @@ return (!ScanInt32(gtext_iter, &ii)) && ((ii < qual_line_mins[1]) || (ii > qual_line_maxs[1])); } +// kDosageParseForceMissing = --import-dosage-certainty filter applied ENUM_U31_DEF_START() kDosageParseOk, kDosageParseMissing, kDosageParseForceMissing ENUM_U31_DEF_END(DosageParseResult); -BoolErr ParseVcfGp(const char* gp_iter, uint32_t is_haploid, double import_dosage_certainty, DosageParseResult* dpr_ptr, double* alt_dosage_ptr) { +BoolErr ParseVcfBiallelicGp(const char* gp_iter, uint32_t is_haploid, double import_dosage_certainty, DosageParseResult* dpr_ptr, double* alt_dosage_ptr) { // P(0/0), P(0/1), P(1/1), etc. // assumes dpr initialized to kDosageParseOk double prob_0alt; @@ -938,7 +949,7 @@ } double alt_dosage; if (dosage_is_gp) { - if (ParseVcfGp(gtext_iter, is_haploid, import_dosage_certainty, dpr_ptr, &alt_dosage)) { + if (ParseVcfBiallelicGp(gtext_iter, is_haploid, import_dosage_certainty, dpr_ptr, &alt_dosage)) { return 1; } } else { @@ -982,7 +993,7 @@ // assumes cur_dphase_delta initialized to 0 // assumes hds_valid initialized to 0 // assumes dosage_field_idx != UINT32_MAX and/or hds_field_idx != UINT32_MAX - // returns 1 if missing OR parsing error. error: no_dosage_here still 0. + // returns 1 if missing OR parsing error. error: dpr still kDosageParseOk. if (hds_field_idx != UINT32_MAX) { // search for HDS first, then DS @@ -2665,16 +2676,23 @@ // reader can be very simple since *only* chromosome filters are supported // by import functions.) Do the same for .bgen and .bcf files. - reterr = InitTextStreamEx(vcfname, 1, kMaxLongLine, max_line_blen, ClipU32(max_thread_ct - 1, 1, 4), &vcf_txs); + reterr = ForceNonFifo(vcfname); if (unlikely(reterr)) { if (reterr == kPglRetOpenFail) { const uint32_t slen = strlen(vcfname); if ((!StrEndsWith(vcfname, ".vcf", slen)) && (!StrEndsWith(vcfname, ".vcf.gz", slen))) { logerrprintfww("Error: Failed to open %s : %s. (--vcf expects a complete filename; did you forget '.vcf' at the end?)\n", vcfname, strerror(errno)); - goto VcfToPgen_ret_1; + } else { + logerrprintfww(kErrprintfFopen, vcfname, strerror(errno)); } + } else { + logerrprintfww(kErrprintfRewind, vcfname); } + goto VcfToPgen_ret_1; + } + reterr = InitTextStreamEx(vcfname, 1, kMaxLongLine, max_line_blen, ClipU32(max_thread_ct - 1, 1, 4), &vcf_txs); + if (unlikely(reterr)) { goto VcfToPgen_ret_TSTREAM_FAIL; } const uint32_t allow_extra_chrs = (misc_flags / kfMiscAllowExtraChrs) & 1; @@ -2789,7 +2807,7 @@ // // Because of how ##contig is handled (we only keep the lines which // correspond to chromosomes/contigs actually present in the VCF, and not - // filtered out), we wait until second pass to write the .pvar. + // filtered out), we wait until the second pass to write the .pvar. if (StrStartsWithUnsafe(&(line_iter[2]), "chrSet=<")) { if (unlikely(chrset_present)) { logerrputs("Error: Multiple ##chrSet header lines in --vcf file.\n"); @@ -2881,7 +2899,7 @@ --format_hds_search; if (!format_hds_search) { if (!format_dosage_relevant) { - logerrputs("Warning: No FORMAT:DS or :HDS field in --vcf file header. Dosages will not be\nimported.\n"); + logerrputs("Warning: No FORMAT:DS or :HDS field in --vcf file header. Dosages will not be\nimported.\n"); } else { logerrputs("Warning: No FORMAT:HDS field in --vcf file header. Dosages will be imported\n(from FORMAT:DS), but phase information will be limited or absent.\n"); } @@ -2890,8 +2908,8 @@ logerrprintfww("Warning: No FORMAT:%s field in --vcf file header. Dosages will not be imported.\n", dosage_import_field); } FinalizeChrset(misc_flags, cip); - // don't call FinalizeChrInfo here, since this may be followed by - // --pmerge, etc. + // don't call FinalizeChrInfo here, since this may be followed by --pmerge, + // etc. if (unlikely(!StrStartsWithUnsafe(line_iter, "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO"))) { snprintf(g_logbuf, kLogbufSize, "Error: Header line %" PRIuPTR " of --vcf file does not have expected field sequence after #CHROM.\n", line_idx); @@ -2993,6 +3011,7 @@ goto VcfToPgen_ret_MISSING_TOKENS; } if (unlikely(S_CAST(uintptr_t, id_end - pos_end) > kMaxIdBlen)) { + putc_unlocked('\n', stdout); snprintf(g_logbuf, kLogbufSize, "Error: Invalid ID on line %" PRIuPTR " of --vcf file (max " MAX_ID_SLEN_STR " chars).\n", line_idx); goto VcfToPgen_ret_MALFORMED_INPUT_WW; } @@ -3133,6 +3152,7 @@ if (alt_ct == 1) { vcf_parse_err = VcfScanBiallelicHdsLine(&vic, format_end, &phase_or_dosage_found, &line_iter); } else { + putc_unlocked('\n', stdout); logerrputs("Error: --vcf multiallelic dosage import is under development.\n"); reterr = kPglRetNotYetSupported; goto VcfToPgen_ret_1; @@ -3160,6 +3180,7 @@ if (unlikely(variant_ct++ == max_variant_ct)) { #ifdef __LP64__ if (variant_ct == 0x7ffffffd) { + putc_unlocked('\n', stdout); logerrputs("Error: " PROG_NAME_STR " does not support more than 2^31 - 3 variants. We recommend using\nother software for very deep studies of small numbers of genomes.\n"); goto VcfToPgen_ret_MALFORMED_INPUT; } @@ -3218,7 +3239,7 @@ BigstackEndReset(bigstack_end_mark); reterr = InitTextStreamEx(vcfname, 1, kMaxLongLine, MAXV(max_line_blen, kTextStreamBlenFast), decompress_thread_ct, &vcf_txs); if (unlikely(reterr)) { - goto VcfToPgen_ret_TSTREAM_REWIND_FAIL; + goto VcfToPgen_ret_TSTREAM_FAIL; } if (calc_thread_ct + decompress_thread_ct > max_thread_ct) { calc_thread_ct = MAXV(1, max_thread_ct - decompress_thread_ct); @@ -3232,11 +3253,12 @@ for (line_idx = 1, line_iter = TextLineEnd(&vcf_txs); ; ++line_idx, line_iter = AdvPastDelim(line_iter, '\n')) { reterr = TextNextLineUnsafe(&vcf_txs, &line_iter); if (unlikely(reterr)) { - goto VcfToPgen_ret_TSTREAM_REWIND_FAIL; + goto VcfToPgen_ret_TSTREAM_FAIL; } if (line_idx == header_line_ct) { break; } + // chrSet skipped here since we call AppendChrsetLine after this loop if (StrStartsWithUnsafe(line_iter, "##fileformat=") || StrStartsWithUnsafe(line_iter, "##fileDate=") || StrStartsWithUnsafe(line_iter, "##source=") || StrStartsWithUnsafe(line_iter, "##FORMAT=") || StrStartsWithUnsafe(line_iter, "##chrSet=")) { continue; } @@ -3271,13 +3293,13 @@ // export. } // force OS-appropriate eoln - char* line_end = AdvToDelim(line_iter, '\n'); + char* line_last = AdvToDelim(line_iter, '\n'); #ifdef _WIN32 - if (line_end[-1] == '\r') { - --line_end; + if (line_last[-1] == '\r') { + --line_last; } // NOT safe to use AppendBinaryEoln here. - if (unlikely(fwrite_checked(line_iter, line_end - line_iter, pvarfile))) { + if (unlikely(fwrite_checked(line_iter, line_last - line_iter, pvarfile))) { goto VcfToPgen_ret_WRITE_FAIL; } if (unlikely(fputs_checked("\r\n", pvarfile))) { @@ -3285,17 +3307,17 @@ } #else char* line_write_end; - if (line_end[-1] == '\r') { - line_write_end = line_end; - line_end[-1] = '\n'; + if (line_last[-1] == '\r') { + line_write_end = line_last; + line_last[-1] = '\n'; } else { - line_write_end = &(line_end[1]); + line_write_end = &(line_last[1]); } if (unlikely(fwrite_checked(line_iter, line_write_end - line_iter, pvarfile))) { goto VcfToPgen_ret_WRITE_FAIL; } #endif - line_iter = line_end; + line_iter = line_last; } char* write_iter = g_textbuf; if (cip->chrset_source) { @@ -3557,7 +3579,7 @@ // of reterr, but this may be useful for bug investigation. reterr = TextNextLineUnsafe(&vcf_txs, &line_iter); if (unlikely(reterr)) { - goto VcfToPgen_ret_TSTREAM_REWIND_FAIL; + goto VcfToPgen_ret_TSTREAM_FAIL; } // 1. check if we skip this variant. chromosome filter and @@ -3635,7 +3657,7 @@ // allow GATK 3.4 <*:DEL> symbolic allele } while ((ucc > ',') || (ucc == '*')); write_iter = memcpya(write_iter, copy_start, linebuf_iter - copy_start); - if (fwrite_ck(writebuf_flush, pvarfile, &write_iter)) { + if (unlikely(fwrite_ck(writebuf_flush, pvarfile, &write_iter))) { goto VcfToPgen_ret_WRITE_FAIL; } if (ucc != ',') { @@ -3648,8 +3670,8 @@ // VCF specification permits whitespace in INFO field, while PVAR // does not. Check for whitespace and error out if necessary. if (unlikely(memchr(filter_end, ' ', info_end - filter_end))) { - snprintf(g_logbuf, kLogbufSize, "Error: INFO field on line %" PRIuPTR " of --vcf file contains a space; this cannot be imported by " PROG_NAME_STR ". Remove or reformat the field before reattempting import.\n", line_idx); - goto VcfToPgen_ret_MALFORMED_INPUT_2N; + snprintf(g_logbuf, kLogbufSize, "Error: INFO field on line %" PRIuPTR " of --vcf file contains a space; this cannot be imported by " PROG_NAME_STR ". Remove or reformat the field before reattempting import.\n", line_idx); + goto VcfToPgen_ret_MALFORMED_INPUT_WWN; } write_iter = memcpya(write_iter, linebuf_iter, info_end - linebuf_iter); } else { @@ -3785,10 +3807,6 @@ putc_unlocked('\n', stdout); TextStreamErrPrint("--vcf file", &vcf_txs); break; - VcfToPgen_ret_TSTREAM_REWIND_FAIL: - putc_unlocked('\n', stdout); - TextStreamErrPrintRewind("--vcf file", &vcf_txs, &reterr); - break; VcfToPgen_ret_THREAD_PARSE: { // Doesn't really cost us anything to report the first error if multiple @@ -3828,11 +3846,13 @@ reterr = kPglRetMalformedInput; break; VcfToPgen_ret_MALFORMED_INPUT_2N: - logputs("\n"); + putc_unlocked('\n', stdout); logerrputsb(); VcfToPgen_ret_MALFORMED_INPUT: reterr = kPglRetMalformedInput; break; + VcfToPgen_ret_MALFORMED_INPUT_WWN: + putc_unlocked('\n', stdout); VcfToPgen_ret_MALFORMED_INPUT_WW: WordWrapB(0); logerrputsb(); @@ -3854,132 +3874,5213 @@ return reterr; } +PglErr BcfHeaderLineIdxCheck(const char* line_iter, uint32_t header_line_idx) { + while (1) { + const char* tag_start = &(line_iter[1]); + if (unlikely(memequal_k(tag_start, "IDX=", 4))) { + // Although this is text, it's not supposed to be human-edited, so we + // deliberately discourage spec-conforming behavior that's different from + // bcftools's straightforward approach of always putting IDX= at the end. + logerrprintfww("Error: Line %u in BCF text header block has IDX= in the center instead of the end of the line; this is not currently supported by " PROG_NAME_STR ". Contact us if you need this to work.\n", header_line_idx); + return kPglRetNotYetSupported; + } + line_iter = AdvPastDelim(tag_start, '='); + if (*line_iter != '"') { + line_iter = strchrnul_n(line_iter, ','); + if (*line_iter == ',') { + continue; + } + return kPglRetSuccess; + } + // Need to worry about backslash-escaped characters. + ++line_iter; + while (1) { + line_iter = strchrnul2_n(line_iter, '\\', '"'); + const char cc = *line_iter; + if (cc == '"') { + break; + } + if (unlikely((cc == '\n') || (line_iter[1] == '\n'))) { + goto BcfHeaderLineIdxCheck_FAIL; + } + line_iter = &(line_iter[2]); + } + ++line_iter; + const char cc = *line_iter; + if (cc == ',') { + continue; + } + if (likely(cc == '\n')) { + return kPglRetSuccess; + } + break; + } + BcfHeaderLineIdxCheck_FAIL: + logerrprintf("Error: Line %u in BCF text header block is malformed.\n", header_line_idx); + return kPglRetMalformedInput; +} + +// Caller's responsibility to check for overread and nonnegativity. +BoolErr ScanBcfTypedInt(const unsigned char** vrec_iterp, uint32_t* uint_ptr) { + const unsigned char* vrec_iter = *vrec_iterp; + const uint32_t type_descriptor_byte = *vrec_iter++; + int32_t ii; + if (type_descriptor_byte == 0x11) { + ii = *R_CAST(const int8_t*, vrec_iter); + ++vrec_iter; + } else if (type_descriptor_byte == 0x12) { + ii = *R_CAST(const int16_t*, vrec_iter); + vrec_iter = &(vrec_iter[2]); + } else if (likely(type_descriptor_byte == 0x13)) { + ii = *R_CAST(const int32_t*, vrec_iter); + vrec_iter = &(vrec_iter[4]); + } else { + return 1; + } + *vrec_iterp = vrec_iter; + *uint_ptr = ii; + return 0; +} -PglErr OxSampleToPsam(const char* samplename, const char* ox_missing_code, ImportFlags import_flags, char* outname, char* outname_end, uint32_t* sample_ct_ptr) { - unsigned char* bigstack_mark = g_bigstack_base; - FILE* psamfile = nullptr; - PglErr reterr = kPglRetSuccess; - uintptr_t line_idx = 0; - TextStream sample_txs; - PreinitTextStream(&sample_txs); - { - uint32_t omp_slen = 2; - char output_missing_pheno[kMaxMissingPhenostrBlen]; - if (import_flags & kfImportKeepAutoconv) { - // must use --output-missing-phenotype parameter, which we've validated - // to be consistent with --input-missing-phenotype - omp_slen = strlen(g_output_missing_pheno); - memcpy(output_missing_pheno, g_output_missing_pheno, omp_slen); - } else { - // use "NA" since that's always safe - memcpy_k(output_missing_pheno, "NA", 2); +// value_type guaranteed to be in 0..15, but otherwise unvalidated. value_ct +// not validated. +static inline BoolErr ScanBcfType(const unsigned char** vrec_iterp, uint32_t* value_type_ptr, uint32_t* value_ct_ptr) { + const uint32_t type_descriptor_byte = **vrec_iterp; + *vrec_iterp += 1; + *value_type_ptr = type_descriptor_byte & 0xf; + *value_ct_ptr = type_descriptor_byte >> 4; + if (*value_ct_ptr != 15) { + return 0; + } + return ScanBcfTypedInt(vrec_iterp, value_ct_ptr); +} + +static inline BoolErr ScanBcfTypeAligned(const unsigned char** vrec_iterp, uint32_t* value_type_ptr, uint32_t* value_ct_ptr) { + const uint32_t type_descriptor_byte = **vrec_iterp; + *value_type_ptr = type_descriptor_byte & 0xf; + *value_ct_ptr = type_descriptor_byte >> 4; + if (*value_ct_ptr != 15) { + *vrec_iterp += kBytesPerVec; + return 0; + } +#ifdef __LP64__ + const unsigned char* vrec_iter_tmp = *vrec_iterp; + ++vrec_iter_tmp; + *vrec_iterp += kBytesPerVec; + return ScanBcfTypedInt(vrec_iterp, value_ct_ptr); +#else + *vrec_iterp += 1; + BoolErr ret_boolerr = ScanBcfTypedInt(vrec_iterp, value_ct_ptr); + VecAlignUp(vrec_iterp); + return ret_boolerr; +#endif +} + +// Check overread within this function, since it can be long enough for integer +// overflow, etc. +BoolErr ScanBcfTypedString(const unsigned char* vrec_end, const unsigned char** vrec_iterp, const char** string_startp, uint32_t* slen_ptr) { + const unsigned char* vrec_iter = *vrec_iterp; + const uint32_t type_descriptor_byte = *vrec_iter++; + if (unlikely((type_descriptor_byte & 0xf) != 7)) { + return 1; + } + uint32_t slen = type_descriptor_byte >> 4; + if (slen == 15) { + if (unlikely(ScanBcfTypedInt(&vrec_iter, &slen) || (S_CAST(int32_t, slen) < 15))) { + return 1; } - const char* missing_catname = g_missing_catname; - uint32_t missing_catname_slen = strlen(missing_catname); + } + if (unlikely(S_CAST(intptr_t, vrec_end - vrec_iter) < S_CAST(intptr_t, slen))) { + return 1; + } + *string_startp = R_CAST(const char*, vrec_iter); + *vrec_iterp = &(vrec_iter[slen]); + *slen_ptr = slen; + return 0; +} - uint32_t max_line_blen; - if (unlikely(StandardizeMaxLineBlen(bigstack_left() / 4, &max_line_blen))) { - goto OxSampleToPsam_ret_NOMEM; +// Unlike the VCF case, everything in BcfImportContext is constant across the +// file. Variation in FORMAT field presence and order is dealt with before the +// handoff to BcfGenoToPgenThread, and UINT32_MAX offset values are used to +// indicate missing fields. +typedef struct BcfImportBaseContextStruct { + uint32_t sample_ct; + VcfHalfCall halfcall_mode; + // [0] = GQ, [1] = DP + STD_ARRAY_DECL(int32_t, 2, qual_mins); + STD_ARRAY_DECL(int32_t, 2, qual_maxs); +#ifdef USE_AVX2 + unsigned char biallelic_gt_lookup[32]; +#else + unsigned char biallelic_gt_lookup[16]; +#endif +} BcfImportBaseContext; + +typedef struct BcfImportContextStruct { + BcfImportBaseContext bibc; + uint32_t dosage_is_gp; + uint32_t dosage_erase_halfdist; + double import_dosage_certainty; +} BcfImportContext; + +ENUM_U31_DEF_START() + kBcfParseOk, + kBcfParseMalformedGeneric, + kBcfParseHalfCallError, + kBcfParseInvalidDosage, + kBcfParseWideGt, + kBcfParseFloatDp, + kBcfParseNonfloatDosage, +ENUM_U31_DEF_END(BcfParseErr); + +// Sets genovec bits to 0b11 whenever GQ/DP doesn't pass. (Caller should pass +// in a separate array if they need to distinguish between ordinary genovec +// missingness vs. missingness due to this filter.) +BcfParseErr BcfParseGqDpMain(const unsigned char* qual_main, uint32_t sample_ct, uint32_t qual_min, uint32_t qual_max, uint32_t qual_value_type, uintptr_t* genovec) { + // need to support int8, int16, int32, and float. +#ifndef __LP64__ + const uint32_t word_ct_m1 = (sample_ct - 1) / kBitsPerWordD2; + uint32_t loop_len = kBitsPerWordD2; +#endif + if (qual_value_type == 1) { + // int8 + if (qual_min > 0x7f) { + SetAllBits(sample_ct * 2, genovec); + return kBcfParseOk; } - reterr = InitTextStream(samplename, max_line_blen, 1, &sample_txs); - if (unlikely(reterr)) { - if (reterr == kPglRetOpenFail) { - const uint32_t slen = strlen(samplename); - if ((!StrEndsWith(samplename, ".sample", slen)) && - (!StrEndsWith(samplename, ".sample.gz", slen))) { - logerrprintfww("Error: Failed to open %s : %s. (--sample expects a complete filename; did you forget '.sample' at the end?)\n", samplename, strerror(errno)); - goto OxSampleToPsam_ret_1; +#ifdef __LP64__ + const uint32_t fullvec_ct = sample_ct / kBytesPerVec; + // Only signed-comparison intrinsics exist. + const VecI8* qual_alias = R_CAST(const VecI8*, qual_main); + const VecI8 min_vec = veci8_set1(qual_min); + Vec4thUint* genovec_alias = R_CAST(Vec4thUint*, genovec); +#else + const int8_t* qual_iter = R_CAST(const int8_t*, qual_main); + const int8_t qual_min_i8 = S_CAST(int8_t, qual_min); +#endif + if (qual_max >= 0x7f) { + // Usual case: safe to skip qual_max comparisons. +#ifdef __LP64__ + for (uint32_t vidx = 0; vidx != fullvec_ct; ++vidx) { + const VecI8 vv = veci8_loadu(&(qual_alias[vidx])); + VecI8 fail_vec = (min_vec > vv); + fail_vec = veci8_permute0xd8_if_avx2(fail_vec); + const VecI8 fail_vec_lo = veci8_unpacklo8(fail_vec, fail_vec); + const VecI8 fail_vec_hi = veci8_unpackhi8(fail_vec, fail_vec); + const Vec4thUint fail_bits_lo = veci8_movemask(fail_vec_lo); + const Vec4thUint fail_bits_hi = veci8_movemask(fail_vec_hi); + genovec_alias[vidx] |= fail_bits_lo | (fail_bits_hi << kBytesPerVec); + } + const uint32_t remainder = sample_ct % kBytesPerVec; + if (remainder) { + const int8_t* trailing_start = R_CAST(const int8_t*, &(qual_main[fullvec_ct * kBytesPerVec])); + const int8_t qual_min_i8 = S_CAST(int8_t, qual_min); + Vec4thUint fail_bits = 0; + for (uint32_t uii = 0; uii != remainder; ++uii) { + if (qual_min_i8 > trailing_start[uii]) { + fail_bits |= (3 * k1LU) << (2 * uii); + } } + genovec_alias[fullvec_ct] |= fail_bits; } - goto OxSampleToPsam_ret_TSTREAM_FAIL; - } - uint32_t mc_ct = 0; - uintptr_t max_mc_blen = 1; - char* sorted_mc = nullptr; - if (!ox_missing_code) { - if (unlikely(bigstack_alloc_c(3, &sorted_mc))) { - goto OxSampleToPsam_ret_NOMEM; +#else + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + uintptr_t geno_word = genovec[widx]; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + if (qual_min_i8 > qual_iter[sample_idx_lowbits]) { + geno_word |= (3 * k1LU) << (2 * sample_idx_lowbits); + } + } + qual_iter = &(qual_iter[kBitsPerWordD2]); + genovec[widx] = geno_word; } - strcpy_k(sorted_mc, "NA"); - mc_ct = 1; - max_mc_blen = 3; +#endif } else { - // er, this should use something like - // CountAndMeasureMultistrReverseAlloc()... - const char* missing_code_iter = ox_missing_code; - while (*missing_code_iter) { - while (*missing_code_iter == ',') { - ++missing_code_iter; + // Two comparisons required, and we need to special-case the missing + // value. + // (There is currently no case where we enforce a maximum but not a + // minimum. Even when --vcf-max-dp is specified without --vcf-min-dp, a + // minimum depth of zero is always enforced, and we're scanning a vector + // of signed values.) +#ifdef __LP64__ + const VecI8 max_vec = veci8_set1(qual_max); + const VecI8 missing_vec = veci8_set1(0x80); + for (uint32_t vidx = 0; vidx != fullvec_ct; ++vidx) { + const VecI8 vv = veci8_loadu(&(qual_alias[vidx])); + const VecI8 fail_or_missing_vec = (min_vec > vv) | (max_vec < vv); + const VecI8 cur_missing_vec = (vv == missing_vec); + VecI8 fail_vec = veci8_and_notfirst(cur_missing_vec, fail_or_missing_vec); + fail_vec = veci8_permute0xd8_if_avx2(fail_vec); + const VecI8 fail_vec_lo = veci8_unpacklo8(fail_vec, fail_vec); + const VecI8 fail_vec_hi = veci8_unpackhi8(fail_vec, fail_vec); + const Vec4thUint fail_bits_lo = veci8_movemask(fail_vec_lo); + const Vec4thUint fail_bits_hi = veci8_movemask(fail_vec_hi); + genovec_alias[vidx] |= fail_bits_lo | (fail_bits_hi << kBytesPerVec); + } + const uint32_t remainder = sample_ct % kBytesPerVec; + if (remainder) { + const int8_t* trailing_start = R_CAST(const int8_t*, &(qual_main[fullvec_ct * kBytesPerVec])); + const int8_t qual_min_i8 = S_CAST(int8_t, qual_min); + const int8_t qual_max_i8 = S_CAST(int8_t, qual_max); + Vec4thUint fail_bits = 0; + for (uint32_t uii = 0; uii != remainder; ++uii) { + const int8_t cur_qual = trailing_start[uii]; + if (((qual_min_i8 > cur_qual) || (qual_max_i8 < cur_qual)) && (cur_qual != -128)) { + fail_bits |= (3 * k1LU) << (2 * uii); + } } - if (!(*missing_code_iter)) { - break; + genovec_alias[fullvec_ct] |= fail_bits; + } +#else + const int8_t qual_max_i8 = S_CAST(int8_t, qual_max); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); } - ++mc_ct; - const char* token_end = strchrnul(missing_code_iter, ','); - uintptr_t token_slen = token_end - missing_code_iter; - if (token_slen >= max_mc_blen) { - max_mc_blen = token_slen + 1; + uintptr_t geno_word = genovec[widx]; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const int8_t cur_qual = qual_iter[sample_idx_lowbits]; + if (((qual_min_i8 > cur_qual) || (qual_max_i8 < cur_qual)) && (cur_qual != -128)) { + geno_word |= (3 * k1LU) << (2 * sample_idx_lowbits); + } } - missing_code_iter = token_end; + qual_iter = &(qual_iter[kBitsPerWordD2]); + genovec[widx] = geno_word; } - if (mc_ct) { - if (unlikely(bigstack_alloc_c(mc_ct * max_mc_blen, &sorted_mc))) { - goto OxSampleToPsam_ret_NOMEM; +#endif + } + } else if (qual_value_type == 2) { + // int16 + if (qual_min > 0x7fff) { + SetAllBits(sample_ct * 2, genovec); + return kBcfParseOk; + } +#ifdef __LP64__ + const uint32_t fullvec_ct = sample_ct / kInt16PerVec; + const VecI16* qual_alias = R_CAST(const VecI16*, qual_main); + const VecI16 min_vec = veci16_set1(qual_min); + Vec8thUint* genovec_alias = R_CAST(Vec8thUint*, genovec); +#else + const int16_t* qual_iter = R_CAST(const int16_t*, qual_main); + const int16_t qual_min_i16 = S_CAST(int16_t, qual_min); +#endif + if (qual_max >= 0x7fff) { +#ifdef __LP64__ + for (uint32_t vidx = 0; vidx != fullvec_ct; ++vidx) { + const VecI16 vv = veci16_loadu(&(qual_alias[vidx])); + const VecI16 fail_vec = (min_vec > vv); + const Vec8thUint fail_bits = veci16_movemask(fail_vec); + genovec_alias[vidx] |= fail_bits; + } + const uint32_t remainder = sample_ct % kBytesPerVec; + if (remainder) { + const int16_t* trailing_start = R_CAST(const int16_t*, &(qual_main[fullvec_ct * kInt16PerVec])); + const int16_t qual_min_i16 = S_CAST(int16_t, qual_min); + Vec8thUint fail_bits = 0; + for (uint32_t uii = 0; uii != remainder; ++uii) { + if (qual_min_i16 > trailing_start[uii]) { + fail_bits |= 3U << (2 * uii); + } } - missing_code_iter = ox_missing_code; - for (uintptr_t mc_idx = 0; mc_idx != mc_ct; ++mc_idx) { - while (*missing_code_iter == ',') { - ++missing_code_iter; + genovec_alias[fullvec_ct] |= fail_bits; + } +#else + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; } - const char* token_end = strchrnul(missing_code_iter, ','); - uintptr_t token_slen = token_end - missing_code_iter; - memcpyx(&(sorted_mc[mc_idx * max_mc_blen]), missing_code_iter, token_slen, '\0'); - missing_code_iter = token_end; + loop_len = ModNz(sample_ct, kBitsPerWordD2); } - // this was temporarily broken in June 2018 due to first - // strcmp_overread() implementation returning 0 instead of -1 on - // less-than - qsort(sorted_mc, mc_ct, max_mc_blen, strcmp_overread_casted); + uintptr_t geno_word = genovec[widx]; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + if (qual_min_i16 > qual_iter[sample_idx_lowbits]) { + geno_word |= (3 * k1LU) << (2 * sample_idx_lowbits); + } + } + qual_iter = &(qual_iter[kBitsPerWordD2]); + genovec[widx] = geno_word; + } +#endif + } else { + // Two comparisons required, and we need to special-case the missing + // value. +#ifdef __LP64__ + const VecI16 max_vec = veci16_set1(qual_max); + const VecI16 missing_vec = veci16_set1(0x8000); + for (uint32_t vidx = 0; vidx != fullvec_ct; ++vidx) { + const VecI16 vv = veci16_loadu(&(qual_alias[vidx])); + const VecI16 fail_or_missing_vec = (min_vec > vv) | (max_vec < vv); + const VecI16 cur_missing_vec = (vv == missing_vec); + const VecI16 fail_vec = veci16_and_notfirst(cur_missing_vec, fail_or_missing_vec); + const Vec8thUint fail_bits = veci16_movemask(fail_vec); + genovec_alias[vidx] |= fail_bits; + } + const uint32_t remainder = sample_ct % kBytesPerVec; + if (remainder) { + const int16_t* trailing_start = R_CAST(const int16_t*, &(qual_main[fullvec_ct * kInt16PerVec])); + const int16_t qual_min_i16 = S_CAST(int16_t, qual_min); + const int16_t qual_max_i16 = S_CAST(int16_t, qual_max); + Vec8thUint fail_bits = 0; + for (uint32_t uii = 0; uii != remainder; ++uii) { + const int16_t cur_qual = trailing_start[uii]; + if (((qual_min_i16 > cur_qual) || (qual_max_i16 < cur_qual)) && (cur_qual != -32768)) { + fail_bits |= 3U << (2 * uii); + } + } + genovec_alias[fullvec_ct] |= fail_bits; + } +#else + const int16_t qual_max_i16 = S_CAST(int16_t, qual_max); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + uintptr_t geno_word = genovec[widx]; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const int16_t cur_qual = qual_iter[sample_idx_lowbits]; + if (((qual_min_i16 > cur_qual) || (qual_max_i16 < cur_qual)) && (cur_qual != -32768)) { + geno_word |= (3 * k1LU) << (2 * sample_idx_lowbits); + } + } + qual_iter = &(qual_iter[kBitsPerWordD2]); + genovec[widx] = geno_word; } +#endif } - - // New first pass: check whether, from the third line on, all first tokens - // are '0'. If yes, we omit FID from the output. - uint32_t write_fid = 0; - uint32_t header_lines_left = 2; - while (1) { - ++line_idx; - char* line_start = TextGet(&sample_txs); - if (!line_start) { - // bugfix (16 Feb 2018): don't check this if we break out of the loop - // on non-0 FID - if (unlikely(TextStreamErrcode2(&sample_txs, &reterr))) { - goto OxSampleToPsam_ret_TSTREAM_FAIL; + } else if (unlikely((qual_value_type == 5) && (qual_max != 0x7fffffff))) { + // Leave out the two-comparison float subcase for now, it's messier and + // floating-point DP is unlikely. + return kBcfParseFloatDp; + } else if ((qual_value_type == 3) || ((qual_value_type == 5) && qual_min)) { + // int32, or typical GQ float + if (qual_value_type == 5) { + // More efficient to perform the comparison in integer-space, since we + // treat all NaN values (0x7f800000...0x7fffffff) as passing. + // We do single out the qual_min == 0 special case, since in that case + // signed-zero (0x80000000) must pass. + + // Convert to the appropriate floating-point bit pattern, rounding up. + const uint32_t orig_qual_min = qual_min; + const float fxx = S_CAST(float, qual_min); + memcpy(&qual_min, &fxx, 4); + if (orig_qual_min > 0x1000000) { + // We need to add 1 to qual_min iff high_bit_idx > low_bit_idx + 23. + const uint32_t high_bit_idx = bsru32(orig_qual_min); + const uint32_t low_bit_idx = ctzu32(orig_qual_min); + qual_min += (high_bit_idx > (low_bit_idx + 23)); + } + } +#ifdef __LP64__ + const uint32_t fullvec_ct = sample_ct / kInt32PerVec; + const VecI32* qual_alias = R_CAST(const VecI32*, qual_main); + const VecI32 min_vec = veci32_set1(qual_min); + Vec16thUint* genovec_alias = R_CAST(Vec16thUint*, genovec); +#else + const int32_t* qual_iter = R_CAST(const int32_t*, qual_main); + const int32_t qual_min_i32 = S_CAST(int32_t, qual_min); +#endif + if (qual_max == 0x7fffffff) { +#ifdef __LP64__ + for (uint32_t vidx = 0; vidx != fullvec_ct; ++vidx) { + const VecI32 vv = veci32_loadu(&(qual_alias[vidx])); + const VecI32 fail_vec = (min_vec > vv); +# ifdef USE_AVX2 + const Vec16thUint fail_bits = _pext_u32(veci32_movemask(fail_vec), 0x33333333); +# else + const __m128i vec_packed = _mm_packs_epi16(R_CAST(__m128i, fail_vec), R_CAST(__m128i, fail_vec)); + // unwanted high bits get truncated here. + const Vec16thUint fail_bits = _mm_movemask_epi8(vec_packed); +# endif + genovec_alias[vidx] |= fail_bits; + } + const uint32_t remainder = sample_ct % kInt32PerVec; + if (remainder) { + const int32_t* trailing_start = R_CAST(const int32_t*, &(qual_main[fullvec_ct * kInt32PerVec])); + const int32_t qual_min_i32 = S_CAST(int32_t, qual_min); + Vec16thUint fail_bits = 0; + for (uint32_t uii = 0; uii != remainder; ++uii) { + if (qual_min_i32 > trailing_start[uii]) { + fail_bits |= 3U << (2 * uii); + } } - if (unlikely(header_lines_left)) { - logerrputs("Error: Empty .sample file.\n"); - goto OxSampleToPsam_ret_MALFORMED_INPUT; + genovec_alias[fullvec_ct] |= fail_bits; + } +#else + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); } - break; + uintptr_t geno_word = genovec[widx]; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + if (qual_min_i32 > qual_iter[sample_idx_lowbits]) { + geno_word |= (3 * k1LU) << (2 * sample_idx_lowbits); + } + } + qual_iter = &(qual_iter[kBitsPerWordD2]); + genovec[widx] = geno_word; } - if (header_lines_left) { - --header_lines_left; - continue; +#endif + } else { + // Two comparisons required, and we need to special-case the missing + // value. +#ifdef __LP64__ + const VecI32 max_vec = veci32_set1(qual_max); + const VecI32 missing_vec = veci32_set1(0x80000000); + for (uint32_t vidx = 0; vidx != fullvec_ct; ++vidx) { + const VecI32 vv = veci32_loadu(&(qual_alias[vidx])); + const VecI32 fail_or_missing_vec = (min_vec > vv) | (max_vec < vv); + const VecI32 cur_missing_vec = (vv == missing_vec); + const VecI32 fail_vec = veci32_and_notfirst(cur_missing_vec, fail_or_missing_vec); +# ifdef USE_AVX2 + const Vec16thUint fail_bits = _pext_u32(veci32_movemask(fail_vec), 0x33333333); +# else + const __m128i vec_packed = _mm_packs_epi16(R_CAST(__m128i, fail_vec), R_CAST(__m128i, fail_vec)); + const Vec16thUint fail_bits = _mm_movemask_epi8(vec_packed); +# endif + genovec_alias[vidx] |= fail_bits; + } + const uint32_t remainder = sample_ct % kInt32PerVec; + if (remainder) { + const int32_t* trailing_start = R_CAST(const int32_t*, &(qual_main[fullvec_ct * kInt32PerVec])); + const int32_t qual_min_i32 = S_CAST(int32_t, qual_min); + const int32_t qual_max_i32 = S_CAST(int32_t, qual_max); + Vec16thUint fail_bits = 0; + for (uint32_t uii = 0; uii != remainder; ++uii) { + const int32_t cur_qual = trailing_start[uii]; + if (((qual_min_i32 > cur_qual) || (qual_max_i32 < cur_qual)) && (cur_qual != -2147483648)) { + fail_bits |= 3U << (2 * uii); + } + } + genovec_alias[fullvec_ct] |= fail_bits; } - if ((line_start[0] != '0') || (!IsSpaceOrEoln(line_start[1]))) { - write_fid = 1; - break; +#else + const int32_t qual_max_i32 = S_CAST(int32_t, qual_max); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + uintptr_t geno_word = genovec[widx]; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const int32_t cur_qual = qual_iter[sample_idx_lowbits]; + if (((qual_min_i32 > cur_qual) || (qual_max_i32 < cur_qual)) && (cur_qual != -2147483648)) { + geno_word |= (3 * k1LU) << (2 * sample_idx_lowbits); + } + } + qual_iter = &(qual_iter[kBitsPerWordD2]); + genovec[widx] = geno_word; } +#endif } - reterr = TextRewind(&sample_txs); - if (unlikely(reterr)) { - goto OxSampleToPsam_ret_TSTREAM_FAIL; - } - line_idx = 1; - char* line_start = TextGet(&sample_txs); - if (unlikely(!line_start)) { - reterr = TextStreamRawErrcode(&sample_txs); - goto OxSampleToPsam_ret_TSTREAM_REWIND_FAIL; + } else if (likely(qual_value_type == 5)) { + // float, qual_min == 0. + // Fail when bit pattern > 0x80000000U. +#ifdef __LP64__ + const uint32_t fullvec_ct = sample_ct / kInt32PerVec; + Vec16thUint* genovec_alias = R_CAST(Vec16thUint*, genovec); +# ifdef USE_AVX2 + // Subtract 1 with wraparound, and then check for < -1. + + // We don't use VecI32 since wraparound is undefined in non-vectorized + // arithmetic, and we don't use VecU32 since cmpgt is signed. + const __m256i* qual_alias = R_CAST(const __m256i*, qual_main); + const __m256i all1 = _mm256_set1_epi8(0xff); + for (uint32_t vidx = 0; vidx != fullvec_ct; ++vidx) { + const __m256i vv = _mm256_loadu_si256(&(qual_alias[vidx])); + const __m256i vv_minus_1 = _mm256_add_epi32(vv, all1); + const __m256i fail_vec = _mm256_cmpgt_epi32(vv_minus_1, all1); + const Vec16thUint fail_bits = _pext_u32(_mm256_movemask_epi8(fail_vec), 0x33333333); + genovec_alias[vidx] |= fail_bits; + } +# else + const __m128i* qual_alias = R_CAST(const __m128i*, qual_main); + const __m128i all1 = _mm_set1_epi8(0xff); + for (uint32_t vidx = 0; vidx != fullvec_ct; ++vidx) { + const __m128i vv = _mm_loadu_si128(&(qual_alias[vidx])); + const __m128i vv_minus_1 = _mm_add_epi32(vv, all1); + const __m128i fail_vec = _mm_cmpgt_epi32(vv_minus_1, all1); + const __m128i vec_packed = _mm_packs_epi16(R_CAST(__m128i, fail_vec), R_CAST(__m128i, fail_vec)); + const Vec16thUint fail_bits = _mm_movemask_epi8(vec_packed); + genovec_alias[vidx] |= fail_bits; + } +# endif + const uint32_t remainder = sample_ct % kInt32PerVec; + if (remainder) { + const uint32_t* trailing_start = R_CAST(const uint32_t*, &(qual_main[fullvec_ct * kInt32PerVec])); + Vec16thUint fail_bits = 0; + for (uint32_t uii = 0; uii != remainder; ++uii) { + if (trailing_start[uii] > 0x80000000U) { + fail_bits |= 3U << (2 * uii); + } + } + genovec_alias[fullvec_ct] |= fail_bits; + } +#else + const uint32_t* qual_iter = R_CAST(const uint32_t*, qual_main); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + uintptr_t geno_word = genovec[widx]; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + if (qual_iter[sample_idx_lowbits] > 0x80000000U) { + geno_word |= (3 * k1LU) << (2 * sample_idx_lowbits); + } + } + qual_iter = &(qual_iter[kBitsPerWordD2]); + genovec[widx] = geno_word; + } +#endif + } else { + return kBcfParseMalformedGeneric; + } + return kBcfParseOk; +} + +BcfParseErr BcfParseGqDpUnaligned(const unsigned char* qual_type_start, uint32_t sample_ct, uint32_t qual_min, uint32_t qual_max, uintptr_t* genovec) { + const unsigned char* qual_iter = qual_type_start; + uint32_t qual_value_type; + uint32_t qual_value_ct; + if (unlikely(ScanBcfType(&qual_iter, &qual_value_type, &qual_value_ct) || (qual_value_ct > 1))) { + return kBcfParseMalformedGeneric; + } + if (!qual_value_ct) { + return kBcfParseOk; + } + return BcfParseGqDpMain(qual_iter, sample_ct, qual_min, qual_max, qual_value_type, genovec); +} + +BcfParseErr BcfParseGqDpAligned(const unsigned char* qual_type_start, uint32_t sample_ct, uint32_t qual_min, uint32_t qual_max, uintptr_t* genovec) { + const unsigned char* qual_iter = qual_type_start; + uint32_t qual_value_type; + uint32_t qual_value_ct; + if (unlikely(ScanBcfTypeAligned(&qual_iter, &qual_value_type, &qual_value_ct) || (qual_value_ct > 1))) { + return kBcfParseMalformedGeneric; + } + if (!qual_value_ct) { + return kBcfParseOk; + } + return BcfParseGqDpMain(qual_iter, sample_ct, qual_min, qual_max, qual_value_type, genovec); +} + +static_assert(kPglMaxAltAlleleCt == 254, "BcfScanGt() needs to be updated."); +BcfParseErr BcfScanGt(const BcfImportContext* bicp, const unsigned char* gt_start, const unsigned char** qual_starts, uint32_t* phase_or_dosage_found_ptr, uintptr_t* invfound_nypbuf) { + // Just check for a phased het. + const unsigned char* gt_main = gt_start; + // Note that generic validation was already performed on these vectors. We + // just need to verify that e.g. GT values are int8/int16, and GQ/DP values + // aren't characters. + uint32_t gt_value_type; + uint32_t gt_value_ct; + if (unlikely(ScanBcfType(>_main, >_value_type, >_value_ct) || (gt_value_type > 2))) { + return (gt_value_type == 3)? kBcfParseWideGt : kBcfParseMalformedGeneric; + } + if (gt_value_ct < 2) { + // ploidy 0 or 1, phased genotypes are impossible + return kBcfParseOk; + } + const uint32_t sample_ct = bicp->bibc.sample_ct; +#ifdef __LP64__ + // Our search is over once we see a phased heterozygous call. Usually, if + // one exists at all, a filter-passing call will be present within the first + // few variants; the important case to optimize is no-phased-genotypes, + // all-diploid. + // So, in the 64-bit diploid case, we perform a pre-scan for a set bottom bit + // in all of the second-genotype-values in each pair. If none exist, there + // can't be any phased (or haploid/0-ploid) genotypes, and we can move on to + // the next variant. + // (Initially tried to make this an exhaustive scan, but then realized + // halfcalls and SIMD don't get along that well.) + if (gt_value_ct == 2) { + if (gt_value_type == 1) { + // int8 + if (sample_ct >= kInt16PerVec) { + const uint32_t vec_ct_m1 = (sample_ct - 1) / kInt16PerVec; + const VecU16* gt_alias = R_CAST(const VecU16*, gt_main); + VecU16 found = vecu16_setzero(); + for (uint32_t vidx = 0; vidx != vec_ct_m1; ++vidx) { + const VecU16 vv = vecu16_loadu(&(gt_alias[vidx])); + found = found | vv; + } + const VecU16 last_vec = vecu16_loadu(&(gt_main[(sample_ct - kInt16PerVec) * 2])); + found = found | last_vec; + if (!vecu16_movemask(vecu16_srli(found, 1))) { + // No phased (or haploid) genotypes at all. + return kBcfParseOk; + } + } + } else { + // int16 + if (sample_ct >= kInt32PerVec) { + const uint32_t vec_ct_m1 = (sample_ct - 1) / kInt32PerVec; + const VecU32* gt_alias = R_CAST(const VecU32*, gt_main); + VecU32 found = vecu32_setzero(); + for (uint32_t vidx = 0; vidx != vec_ct_m1; ++vidx) { + const VecU32 vv = vecu32_loadu(&(gt_alias[vidx])); + found = found | vv; + } + const VecU32 last_vec = vecu32_loadu(&(gt_main[(sample_ct - kInt32PerVec) * 4])); + found = found | last_vec; + if (!(vecu32_movemask(vecu32_srli(found, 1)) & 0x22222222U)) { + return kBcfParseOk; + } + } + } + } +#endif + const VcfHalfCall halfcall_mode = bicp->bibc.halfcall_mode; + if ((!qual_starts[0]) && (!qual_starts[1])) { + // No gq/dp check, so we can exit as soon as we find a match. + if (gt_value_type == 1) { + // int8 + if (gt_value_ct == 2) { + // Usual case. + // Low bit of each second byte is set iff the genotype is either phased + // or non-diploid (0x81 "END_OF_VECTOR"). + const unsigned char* second_byte_stop = &(gt_main[sample_ct * 2 + 1]); + for (const unsigned char* second_byte_iter = &(gt_main[1]); second_byte_iter != second_byte_stop; second_byte_iter = &(second_byte_iter[2])) { + const uint32_t second_byte = *second_byte_iter; + if ((second_byte & 0x81) == 1) { + // phased + const uint32_t first_allele_idx_p1 = second_byte_iter[-1] >> 1; + const uint32_t second_allele_idx_p1 = second_byte >> 1; + if (first_allele_idx_p1 != second_allele_idx_p1) { + // phased het or halfcall + // (or malformed, false positive ok there) + if (first_allele_idx_p1 && second_allele_idx_p1) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + if (halfcall_mode == kVcfHalfCallReference) { + if (first_allele_idx_p1 + second_allele_idx_p1 != 1) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } + } + } + } + } else { + // Ploidy > 2 treated as missing. + // possible todo: Add SIMD code for the triploid case (advance 15 or 30 + // bytes on each iteration, etc.), since it does realistically come up + // in large datasets, and it only takes one triploid sample to force + // this code path to be taken for all samples. + // (Tetraploidy is much rarer than triploidy.) + const unsigned char* second_byte_stop = &(gt_main[sample_ct * gt_value_type + 1]); + for (const unsigned char* second_byte_iter = &(gt_main[1]); second_byte_iter != second_byte_stop; second_byte_iter = &(second_byte_iter[gt_value_type])) { + uint16_t second_and_third_bytes; + memcpy(&second_and_third_bytes, second_byte_iter, 2); + if ((second_and_third_bytes & 0x8181) == 0x8101) { + // phased diploid + const uint32_t first_allele_idx_p1 = second_byte_iter[-1] >> 1; + const uint32_t second_allele_idx_p1 = (second_and_third_bytes & 0xff) >> 1; + if (first_allele_idx_p1 != second_allele_idx_p1) { + // phased het or halfcall + if (first_allele_idx_p1 && second_allele_idx_p1) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + if (halfcall_mode == kVcfHalfCallReference) { + if (first_allele_idx_p1 + second_allele_idx_p1 != 1) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } + } + } + } + } + } else { + // int16 + if (gt_value_ct == 2) { + const uint16_t* second_u16_stop = R_CAST(const uint16_t*, &(gt_main[sample_ct * 4 + 2])); + for (const uint16_t* second_u16_iter = R_CAST(const uint16_t*, &(gt_main[2])); second_u16_iter != second_u16_stop; second_u16_iter = &(second_u16_iter[2])) { + const uint32_t second_u16 = *second_u16_iter; + if ((second_u16 & 0x8001) == 1) { + // phased + const uint32_t first_allele_idx_p1 = second_u16_iter[-1] >> 1; + const uint32_t second_allele_idx_p1 = second_u16 >> 1; + if (first_allele_idx_p1 != second_allele_idx_p1) { + // phased het or halfcall + if (first_allele_idx_p1 && second_allele_idx_p1) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + if (halfcall_mode == kVcfHalfCallReference) { + if (first_allele_idx_p1 + second_allele_idx_p1 != 1) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } + } + } + } + } else { + // ploidy > 2 + const uint16_t* second_u16_stop = R_CAST(const uint16_t*, &(gt_main[sample_ct * 2 * gt_value_type + 2])); + for (const uint16_t* second_u16_iter = R_CAST(const uint16_t*, &(gt_main[2])); second_u16_iter != second_u16_stop; second_u16_iter = &(second_u16_iter[gt_value_type])) { + uint32_t second_and_third_u16s; + memcpy(&second_and_third_u16s, second_u16_iter, 4); + if ((second_and_third_u16s & 0x80018001U) == 0x80010001U) { + // phased diploid + const uint32_t first_allele_idx_p1 = second_u16_iter[-1] >> 1; + const uint32_t second_allele_idx_p1 = (second_and_third_u16s & 0xffff) >> 1; + if (first_allele_idx_p1 != second_allele_idx_p1) { + // phased het or halfcall + if (first_allele_idx_p1 && second_allele_idx_p1) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + if (halfcall_mode == kVcfHalfCallReference) { + if (first_allele_idx_p1 + second_allele_idx_p1 != 1) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } + } + } + } + } + } + return kBcfParseOk; + } + // We clear a bit when we find a phase/dosage we're keeping; then + // BcfParseGqDpUnaligned() sets it iff the GQ or DP filter fails for that + // sample; if any clear bits remain at the end, we're done with our scan. + SetAllBits(sample_ct * 2, invfound_nypbuf); + if (gt_value_type == 1) { + if (gt_value_ct == 2) { + const unsigned char* second_byte_start = &(gt_main[1]); + for (uint32_t sample_idx = 0; sample_idx != sample_ct; ++sample_idx) { + const uint32_t second_byte = second_byte_start[sample_idx * 2]; + if ((second_byte & 0x81) == 1) { + // phased + const uint32_t first_allele_idx_p1 = second_byte_start[sample_idx * 2 - 1] >> 1; + const uint32_t second_allele_idx_p1 = second_byte >> 1; + if (first_allele_idx_p1 != second_allele_idx_p1) { + // phased het or halfcall + if (first_allele_idx_p1 && second_allele_idx_p1) { + ClearBit(sample_idx * 2, invfound_nypbuf); + } else if (halfcall_mode == kVcfHalfCallReference) { + if (first_allele_idx_p1 + second_allele_idx_p1 != 1) { + ClearBit(sample_idx * 2, invfound_nypbuf); + } + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } + } + } + } + } else { + // ploidy > 2 + const unsigned char* second_byte_iter = &(gt_main[1]); + for (uint32_t sample_idx = 0; sample_idx != sample_ct; ++sample_idx, second_byte_iter = &(second_byte_iter[gt_value_type])) { + uint16_t second_and_third_bytes; + memcpy(&second_and_third_bytes, second_byte_iter, 2); + if ((second_and_third_bytes & 0x8181) == 0x8101) { + // phased diploid + const uint32_t first_allele_idx_p1 = second_byte_iter[-1] >> 1; + const uint32_t second_allele_idx_p1 = (second_and_third_bytes & 0xff) >> 1; + if (first_allele_idx_p1 != second_allele_idx_p1) { + // phased het or halfcall + if (first_allele_idx_p1 && second_allele_idx_p1) { + ClearBit(sample_idx * 2, invfound_nypbuf); + } else if (halfcall_mode == kVcfHalfCallReference) { + if (first_allele_idx_p1 + second_allele_idx_p1 != 1) { + ClearBit(sample_idx * 2, invfound_nypbuf); + } + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } + } + } + } + } + } else { + // int16 + if (gt_value_ct == 2) { + const uint16_t* second_u16_start = R_CAST(const uint16_t*, &(gt_main[2])); + for (uint32_t sample_idx = 0; sample_idx != sample_ct; ++sample_idx) { + const uint32_t second_u16 = second_u16_start[sample_idx * 2]; + if ((second_u16 & 0x8001) == 1) { + // phased + const uint32_t first_allele_idx_p1 = second_u16_start[sample_idx * 2 - 1] >> 1; + const uint32_t second_allele_idx_p1 = second_u16 >> 1; + if (first_allele_idx_p1 != second_allele_idx_p1) { + // phased het or halfcall + if (first_allele_idx_p1 && second_allele_idx_p1) { + ClearBit(sample_idx * 2, invfound_nypbuf); + } else if (halfcall_mode == kVcfHalfCallReference) { + if (first_allele_idx_p1 + second_allele_idx_p1 != 1) { + ClearBit(sample_idx * 2, invfound_nypbuf); + } + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } + } + } + } + } else { + // ploidy > 2 + const uint16_t* second_u16_iter = R_CAST(const uint16_t*, &(gt_main[2])); + for (uint32_t sample_idx = 0; sample_idx != sample_ct; ++sample_idx, second_u16_iter = &(second_u16_iter[gt_value_type])) { + uint32_t second_and_third_u16s; + memcpy(&second_and_third_u16s, second_u16_iter, 4); + if ((second_and_third_u16s & 0x80018001U) == 0x80010001U) { + // phased diploid + const uint32_t first_allele_idx_p1 = second_u16_iter[-1] >> 1; + const uint32_t second_allele_idx_p1 = (second_and_third_u16s & 0xffff) >> 1; + if (first_allele_idx_p1 != second_allele_idx_p1) { + // phased het or halfcall + if (first_allele_idx_p1 && second_allele_idx_p1) { + ClearBit(sample_idx * 2, invfound_nypbuf); + } else if (halfcall_mode == kVcfHalfCallReference) { + if (first_allele_idx_p1 + second_allele_idx_p1 != 1) { + ClearBit(sample_idx * 2, invfound_nypbuf); + } + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } + } + } + } + } + } + if (AllBitsAreOne(invfound_nypbuf, sample_ct * 2)) { + return kBcfParseOk; + } + for (uint32_t qual_idx = 0; qual_idx != 2; ++qual_idx) { + if (!qual_starts[qual_idx]) { + continue; + } + BcfParseErr bcf_parse_err = BcfParseGqDpUnaligned(qual_starts[qual_idx], sample_ct, bicp->bibc.qual_mins[qual_idx], bicp->bibc.qual_maxs[qual_idx], invfound_nypbuf); + if (bcf_parse_err != kBcfParseOk) { + return bcf_parse_err; + } + if (AllBitsAreOne(invfound_nypbuf, sample_ct * 2)) { + return kBcfParseOk; + } + } + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; +} + +// Dosage parsing is messy enough that we stick as closely as possible to the +// VCF logic, instead of trying to vectorize. +BoolErr ParseBcfBiallelicGp(const float* cur_gp_start, uint32_t is_haploid, double import_dosage_certainty, DosageParseResult* dpr_ptr, double* alt_dosage_ptr) { + // See ParseVcfBiallelicGp(). + // P(0/0), P(0/1), P(1/1), etc. + // assumes dpr initialized to kDosageParseOk + // assumes *gp_iter is not missing + // returns 1 if missing OR parsing error. error: dpr still kDosageParseOk + const float prob_0altf = cur_gp_start[0]; + if (unlikely((prob_0altf < S_CAST(float, 0.0)) || (prob_0altf > S_CAST(float, 1.0)))) { + return 1; + } + const float prob_1altf = cur_gp_start[1]; + // second predicate written to be true on NaN + if (unlikely((prob_1altf < S_CAST(float, 0.0)) || (!(prob_1altf <= S_CAST(float, 1.0))))) { + return 1; + } + const double prob_0alt = S_CAST(double, prob_0altf); + const double prob_1alt = S_CAST(double, prob_1altf); + if (is_haploid) { + const double denom = prob_0alt + prob_1alt; + if (denom <= 2 * import_dosage_certainty) { + if ((prob_0alt <= import_dosage_certainty) && (prob_1alt <= import_dosage_certainty)) { + *dpr_ptr = kDosageParseForceMissing; + return 1; + } + } + *alt_dosage_ptr = 2 * prob_1alt / denom; + return 0; + } + const float prob_2altf = cur_gp_start[2]; + if (unlikely((prob_2altf < S_CAST(float, 0.0)) || (!(prob_2altf <= S_CAST(float, 1.0))))) { + return 1; + } + const double prob_2alt = S_CAST(double, prob_2altf); + const double denom = prob_0alt + prob_1alt + prob_2alt; + if (denom <= 3 * import_dosage_certainty) { + if ((prob_0alt <= import_dosage_certainty) && (prob_1alt <= import_dosage_certainty) && (prob_2alt <= import_dosage_certainty)) { + // force-missing + // ok to use <= since we multiplied by (1 - epsilon) + // during command-line parsing. this lets us avoid + // special-casing denom=0. + *dpr_ptr = kDosageParseForceMissing; + return 1; // not really an error + } + } + *alt_dosage_ptr = (prob_1alt + 2 * prob_2alt) / denom; + return 0; +} + +BoolErr ParseBcfBiallelicDosage(const float* cur_dosage_start, uint32_t is_haploid_or_0ploid, uint32_t dosage_is_gp, double import_dosage_certainty, DosageParseResult* dpr_ptr, uint32_t* dosage_int_ptr) { + // See ParseVcfBiallelicDosage(). + // assumes dpr initialized to kDosageParseOk + // returns 1 if missing OR parsing error. error: dpr still kDosageParseOk. + int32_t first_bits; + memcpy(&first_bits, cur_dosage_start, 4); + if (first_bits > 0x7f800000) { + // ploidy-0, regular missing call, or NaN. + *dpr_ptr = kDosageParseMissing; + return 1; + } + double alt_dosage; + if (dosage_is_gp) { + if (ParseBcfBiallelicGp(cur_dosage_start, is_haploid_or_0ploid, import_dosage_certainty, dpr_ptr, &alt_dosage)) { + return 1; + } + } else { + alt_dosage = S_CAST(double, *cur_dosage_start); + if (unlikely(alt_dosage < 0.0)) { + return 1; + } + if (is_haploid_or_0ploid) { + // possible todo: allow this to be suppressed (maybe upstream of this + // function); 1000 Genomes phase 1 haploid dosages are still on 0..2 + // scale + // right now the best approach for importing those files is commenting + // out this line and recompiling... + if (import_dosage_certainty != 0.0) { + // quasi-bugfix (19 Feb 2019): dosage=DS import should respect + // --import-dosage-certainty + if (((1.0 - alt_dosage) <= import_dosage_certainty) && (alt_dosage <= import_dosage_certainty)) { + *dpr_ptr = kDosageParseForceMissing; + return 1; + } + } + alt_dosage *= 2; + } else { + if (import_dosage_certainty != 0.0) { + const double dist_from_1 = fabs(1.0 - alt_dosage); + if ((1.0 - dist_from_1 <= import_dosage_certainty) && (dist_from_1 <= import_dosage_certainty)) { + *dpr_ptr = kDosageParseForceMissing; + return 1; + } + } + } + if (unlikely(alt_dosage > 2.0)) { + return 1; + } + } + *dosage_int_ptr = S_CAST(int32_t, alt_dosage * kDosageMid + 0.5); + return 0; +} + +BoolErr ParseBcfBiallelicHds(const float* dosage_main, const float* hds_main, uint32_t dosage_value_ct, uint32_t hds_value_ct, uint32_t sample_idx, uint32_t is_haploid_or_0ploid, uint32_t dosage_is_gp, double import_dosage_certainty, DosageParseResult* dpr_ptr, uint32_t* dosage_int_ptr, int32_t* cur_dphase_delta_ptr, uint32_t* hds_valid_ptr) { + // See ParseVcfBiallelicHds(). + // assumes dpr initialized to kDosageParseOk + // assumes cur_dphase_delta initialized to 0 + // assumes hds_valid initialized to 0 + // assumes dosage_main != nullptr and/or hds_main != nullptr + // returns 1 if missing OR parsing error. error: dpr still kDosageParseOk. + if (hds_main) { + const float* cur_hds_start = &(hds_main[sample_idx * hds_value_ct]); + // search for HDS first, then DS + int32_t first_bits; + memcpy(&first_bits, cur_hds_start, 4); + if (first_bits <= 0x7f800000) { + double dosage1 = S_CAST(double, cur_hds_start[0]); + if (unlikely((dosage1 < 0.0) || (dosage1 > 1.0))) { + return 1; + } + // if hds_valid and (cur_dphase_delta == 0), caller should override + // hardcall-phase + *hds_valid_ptr = 1; + int32_t second_bits; + memcpy(&second_bits, &(cur_hds_start[1]), 4); + if (second_bits > 0x7f800000) { + // haploid ok, half-call not ok + // 0x7f800002 == END_OF_VECTOR + if (unlikely(second_bits != 0x7f800002)) { + return 1; + } + if (import_dosage_certainty != 0.0) { + if ((1.0 - dosage1 <= import_dosage_certainty) && (dosage1 <= import_dosage_certainty)) { + *dpr_ptr = kDosageParseForceMissing; + return 1; + } + } + *dosage_int_ptr = S_CAST(int32_t, dosage1 * kDosageMax + 0.5); + return 0; + } + double dosage2 = S_CAST(double, cur_hds_start[1]); + if (unlikely((dosage2 < 0.0) || (dosage2 > 1.0))) { + return 1; + } + const double dosage_sum = dosage1 + dosage2; + if (import_dosage_certainty != 0.0) { + // Assume maximal het probability. + const double dist_from_1 = fabs(1.0 - dosage_sum); + if ((1.0 - dist_from_1 <= import_dosage_certainty) && (dist_from_1 <= import_dosage_certainty)) { + *dpr_ptr = kDosageParseForceMissing; + return 1; + } + } + + // force this to be nonnegative, since static_cast rounds + // negative numbers toward zero + const double dosage_diffp1 = 1.0 + dosage1 - dosage2; + + *dosage_int_ptr = S_CAST(int32_t, dosage_sum * kDosageMid + 0.5); + *cur_dphase_delta_ptr = S_CAST(int32_t, dosage_diffp1 * kDosageMid + 0.5) - kDosageMid; + return 0; + } + if (!dosage_main) { + *dpr_ptr = kDosageParseMissing; + return 1; + } + } + const float* cur_dosage_start = &(dosage_main[sample_idx * dosage_value_ct]); + return ParseBcfBiallelicDosage(cur_dosage_start, is_haploid_or_0ploid, dosage_is_gp, import_dosage_certainty, dpr_ptr, dosage_int_ptr); +} + +uint32_t BcfGtIsPhasedHet(uint16_t gt_first, uint16_t gt_second, uint16_t gt_high_bit, VcfHalfCall halfcall_mode) { + if (!(gt_second & 1)) { + return 0; + } + const uint16_t first_allele_idx_p1 = (gt_first & (~gt_high_bit)) >> 1; + const uint16_t second_allele_idx_p1 = (gt_second & (~gt_high_bit)) >> 1; + if (first_allele_idx_p1 == second_allele_idx_p1) { + return 0; + } + return (first_allele_idx_p1 && second_allele_idx_p1) || ((halfcall_mode == kVcfHalfCallReference) && (first_allele_idx_p1 + second_allele_idx_p1 != 1)); +} + +BcfParseErr BcfScanBiallelicHds(const BcfImportContext* bicp, const unsigned char* gt_start, const unsigned char** qual_starts, const unsigned char* dosage_start, const unsigned char* hds_start, uint32_t* phase_or_dosage_found_ptr, uintptr_t* __restrict invfound_nypbuf) { + // See VcfScanBiallelicHdsLine(). + // DS+HDS + // Only need to find phase *or* dosage. We can expect this to happen + // quickly (when we don't, it's essentially user error), so (unlike scanning + // GT for a phased call) there's little point in spending much effort on + // optimizing this. + const float* hds_main = nullptr; + uint32_t hds_value_ct = 0; + if (hds_start) { + const unsigned char* hds_main_raw = hds_start; + uint32_t hds_value_type; + if (unlikely(ScanBcfType(&hds_main_raw, &hds_value_type, &hds_value_ct))) { + return kBcfParseMalformedGeneric; + } + if (unlikely(hds_value_type != 5)) { + return kBcfParseNonfloatDosage; + } + if (hds_value_ct) { + hds_main = R_CAST(const float*, hds_main_raw); + } + } + const float* dosage_main = nullptr; + uint32_t dosage_value_ct = 0; + if (dosage_start) { + const unsigned char* dosage_main_raw = dosage_start; + uint32_t dosage_value_type; + if (unlikely(ScanBcfType(&dosage_main_raw, &dosage_value_type, &dosage_value_ct))) { + return kBcfParseMalformedGeneric; + } + if (unlikely(dosage_value_type != 5)) { + return kBcfParseNonfloatDosage; + } + if (dosage_value_ct) { + dosage_main = R_CAST(const float*, dosage_main_raw); + } + } + if ((!hds_value_ct) && (!dosage_value_ct)) { + if (!gt_start) { + // everything missing + return kBcfParseOk; + } + return BcfScanGt(bicp, gt_start, qual_starts, phase_or_dosage_found_ptr, invfound_nypbuf); + } + const uint32_t sample_ct = bicp->bibc.sample_ct; + ZeroWArr(NypCtToWordCt(sample_ct), invfound_nypbuf); + for (uint32_t qual_idx = 0; qual_idx != 2; ++qual_idx) { + if (!qual_starts[qual_idx]) { + continue; + } + BcfParseErr bcf_parse_err = BcfParseGqDpUnaligned(qual_starts[qual_idx], sample_ct, bicp->bibc.qual_mins[qual_idx], bicp->bibc.qual_maxs[qual_idx], invfound_nypbuf); + if (bcf_parse_err != kBcfParseOk) { + return bcf_parse_err; + } + if (AllBitsAreOne(invfound_nypbuf, sample_ct * 2)) { + return kBcfParseOk; + } + } + const unsigned char* gt_main = gt_start; + uint32_t gt_value_type = 0; + uint32_t gt_value_ct = 0; + if (gt_main) { + if (unlikely(ScanBcfType(>_main, >_value_type, >_value_ct) || (gt_value_type > 3))) { + return kBcfParseMalformedGeneric; + } + if (unlikely(gt_value_type > 1)) { + return kBcfParseWideGt; + } + } + const uint32_t dosage_is_gp = bicp->dosage_is_gp; + const uint32_t dosage_erase_halfdist = bicp->dosage_erase_halfdist; + const double import_dosage_certainty = bicp->import_dosage_certainty; + const VcfHalfCall halfcall_mode = bicp->bibc.halfcall_mode; + uint16_t gt_first = 0; + uint16_t gt_second = 0; + uint32_t is_haploid_or_0ploid = (gt_value_ct == 1); + for (uint32_t sample_idx = 0; sample_idx != sample_ct; ++sample_idx) { + if (IsSet(invfound_nypbuf, sample_idx * 2)) { + continue; + } + if (gt_value_ct > 1) { + // no need to look at gt_first in haploid case. + // only int8 supported, since variant is biallelic + gt_first = gt_main[sample_idx * gt_value_ct]; + gt_second = gt_main[sample_idx * gt_value_ct + 1]; + is_haploid_or_0ploid = gt_second >> 7; + } + DosageParseResult dpr = kDosageParseOk; + int32_t cur_dphase_delta = 0; + uint32_t hds_valid = 0; + uint32_t dosage_int; + if (ParseBcfBiallelicHds(dosage_main, hds_main, dosage_value_ct, hds_value_ct, sample_idx, is_haploid_or_0ploid, dosage_is_gp, import_dosage_certainty, &dpr, &dosage_int, &cur_dphase_delta, &hds_valid)) { + if (unlikely(!dpr)) { + return kBcfParseInvalidDosage; + } + // if dpr != kDosageParseForceMissing, DS and HDS are missing. + if ((dpr != kDosageParseForceMissing) && BcfGtIsPhasedHet(gt_first, gt_second, 0x80, halfcall_mode)) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + } else if (cur_dphase_delta) { + const uint32_t dphase_side1 = dosage_int + cur_dphase_delta; + const uint32_t dphase_side2 = dosage_int - cur_dphase_delta; + const uint32_t dphase_halfdist1 = DphaseHalfdist(dphase_side1); + const uint32_t dphase_halfdist2 = DphaseHalfdist(dphase_side2); + const uint32_t dphase_erase_halfdist = dosage_erase_halfdist + kDosage4th; + if ((dphase_halfdist1 < dphase_erase_halfdist) || + (dphase_halfdist2 < dphase_erase_halfdist) || + (((dphase_side1 + kDosageMid) ^ (dphase_side2 + kDosageMid)) & kDosageMax)) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + } else { + const uint32_t cur_halfdist = BiallelicDosageHalfdist(dosage_int); + if ((cur_halfdist < dosage_erase_halfdist) || + ((!hds_valid) && BcfGtIsPhasedHet(gt_first, gt_second, 0x80, halfcall_mode))) { + *phase_or_dosage_found_ptr = 1; + return kBcfParseOk; + } + } + } + return kBcfParseOk; +} + +void BcfConvertBiallelicHaploidGt(const unsigned char* gt_main, uint32_t sample_ct, uintptr_t* __restrict genovec) { + // Haploid, single byte per genotype. + // 0 -> 3 + // 2 -> 0 + // 4 -> 2 + // 0x81 -> 3 + // Other values invalid, but we don't promise exhaustive error-checking, we + // just need to (i) not blow up and (ii) try to have consistent behavior + // between build types. + // single-byte values, subtract 2 with wraparound, MIN(result, 3) works. +#ifdef __LP64__ + const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); + genovec[sample_ctl2 - 1] = 0; + // overread ok since we perform no error detection + const uint32_t vec_ct = DivUp(sample_ct, kBytesPerVec); + const VecUc* gt_alias = R_CAST(const VecUc*, gt_main); + Vec4thUint* genovec_alias = R_CAST(Vec4thUint*, genovec); + const VecUc neg2 = vecuc_set1(0xfe); + const VecUc three = vecuc_set1(3); + const VecW zero = vecw_setzero(); + for (uint32_t vidx = 0; vidx != vec_ct; ++vidx) { + const VecUc bcf_bytes = vecuc_loadu(&(gt_alias[vidx])); + const VecUc bcf_bytes_m2 = bcf_bytes + neg2; + VecUc converted_bytes = vecuc_min(bcf_bytes_m2, three); + // Now gather bits 0,1,8,9,... via movemask. + converted_bytes = vecuc_permute0xd8_if_avx2(converted_bytes); + VecW vec_lo = vecw_unpacklo8(R_CAST(VecW, converted_bytes), zero); + VecW vec_hi = vecw_unpackhi8(R_CAST(VecW, converted_bytes), zero); + vec_lo = vecw_slli(vec_lo, 7) | vecw_slli(vec_lo, 14); + vec_hi = vecw_slli(vec_hi, 7) | vecw_slli(vec_hi, 14); + const Vec4thUint lo_bits = vecw_movemask(vec_lo); + const Vec4thUint hi_bits = vecw_movemask(vec_hi); + genovec_alias[vidx] = lo_bits | (hi_bits << kBytesPerVec); + } + // Zero out the garbage at the end. + const uint32_t inv_remainder = (-sample_ct) & (kBytesPerVec - 1); + genovec_alias[vec_ct - 1] &= (~S_CAST(Vec4thUint, 0)) >> (2 * inv_remainder); +#else + const unsigned char* gt_iter = gt_main; + const uint32_t word_ct_m1 = (sample_ct - 1) / kBitsPerWordD2; + uint32_t loop_len = kBitsPerWordD2; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + uintptr_t geno_word = 0; + // possible todo: benchmark this vs. the forward loop, with and without + // sparse-optimization + for (uint32_t sample_idx_lowbits = loop_len; sample_idx_lowbits; ) { + --sample_idx_lowbits; + unsigned char raw_val_m2 = gt_iter[sample_idx_lowbits] - 2; + if (raw_val_m2 > 3) { + raw_val_m2 = 3; + } + geno_word |= raw_val_m2; + geno_word = geno_word << 2; + } + genovec[widx] = geno_word; + gt_iter = &(gt_iter[kBitsPerWordD2]); + } +#endif +} + +BcfParseErr BcfApplyGqDpFilters(const BcfImportBaseContext* bibcp, const GparseReadBcfMetadata* metap, const unsigned char* record_start, uintptr_t* __restrict genovec) { + if ((metap->qual_vec_offsets[0] != UINT32_MAX) || (metap->qual_vec_offsets[1] != UINT32_MAX)) { + const uint32_t sample_ct = bibcp->sample_ct; + for (uint32_t qual_idx = 0; qual_idx != 2; ++qual_idx) { + const uint32_t vec_offset = metap->qual_vec_offsets[qual_idx]; + if (vec_offset == UINT32_MAX) { + continue; + } + const unsigned char* qual_start = &(record_start[vec_offset * S_CAST(uintptr_t, kBytesPerVec)]); + BcfParseErr bcf_parse_err = BcfParseGqDpAligned(qual_start, sample_ct, bibcp->qual_mins[qual_idx], bibcp->qual_maxs[qual_idx], genovec); + if (bcf_parse_err != kBcfParseOk) { + return bcf_parse_err; + } + } + } + return kBcfParseOk; +} + +// GT present, dosage/HDS not present, phase known to be irrelevant. +BcfParseErr BcfConvertUnphasedBiallelic(const BcfImportBaseContext* bibcp, const GparseReadBcfMetadata* metap, unsigned char* record_start, uintptr_t* __restrict genovec) { + unsigned char* gt_type_start = &(record_start[metap->gt_vec_offset * S_CAST(uintptr_t, kBytesPerVec)]); + const unsigned char* gt_main = gt_type_start; + // this was partially validated in first pass + uint32_t value_type; + uint32_t value_ct; + ScanBcfTypeAligned(>_main, &value_type, &value_ct); + if (unlikely(value_type != 1)) { + return (value_type <= 3)? kBcfParseWideGt : kBcfParseMalformedGeneric; + } + const uint32_t sample_ct = bibcp->sample_ct; + // value_ct guaranteed to be positive + if (value_ct == 1) { + BcfConvertBiallelicHaploidGt(gt_main, sample_ct, genovec); + } else { + const unsigned char* biallelic_gt_lookup = bibcp->biallelic_gt_lookup; +#ifdef USE_SSE42 + const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); + genovec[sample_ctl2 - 1] = 0; +#else + const uint32_t word_ct_m1 = (sample_ct - 1) / kBitsPerWordD2; + uint32_t loop_len = kBitsPerWordD2; +#endif + if (value_ct == 2) { + // Regular diploid. + // Possibly phased, we ignore that since the prescan told us no phased + // calls are hets. + // After right-shifting, the four cases for each byte are 0, 1, 2, and + // 0x40. This adds up to 16 cases for the byte pair, which works nicely + // with _mm{256}_shuffle_epi8() parallel-lookup. Conveniently, all + // missing, haploid, and non-error half-call handling logic can be + // encoded in the table. + // As for erroring out on half-calls, that's handled by setting the + // relevant table entries to 4 instead of 0..3. +#ifdef USE_SSE42 + const uint32_t vec_ct = DivUp(sample_ct, kInt16PerVec); + const uint32_t inv_remainder = (-sample_ct) & (kInt16PerVec - 1); + if (inv_remainder) { + // Set all past-the-end bytes to 2. This causes trailing genovec bits + // to be zero, and doesn't create a half_call_error_bit2_vec false + // positive. + // (yes, it's a bit silly to use gt_type_start instead of gt_main + // here...) + memset(&(gt_type_start[kBytesPerVec + sample_ct * 2]), 2, 2 * inv_remainder); + } + const VecU16* gt_alias = R_CAST(const VecU16*, gt_main); + Vec8thUint* genovec_alias = R_CAST(Vec8thUint*, genovec); + + const VecU16 hibit_mask = vecu16_set1(0x7f7f); + const VecU16 three = vecu16_set1(0x303); + const VecU16 mask_000f = vecu16_set1(0xf); + const VecU16 lookup_vec = vecu16_loadu(biallelic_gt_lookup); + VecU16 half_call_error_bit2_vec = vecu16_setzero(); + for (uint32_t vidx = 0; vidx != vec_ct; ++vidx) { + const VecU16 bcf_bytes = vecu16_loadu(&(gt_alias[vidx])); + const VecU16 shifted_bytes = vecu16_srli(bcf_bytes, 1) & hibit_mask; + const VecU16 capped_bytes = vecu16_min8(shifted_bytes, three); + const VecU16 ready_for_lookup = (capped_bytes | vecu16_srli(capped_bytes, 6)) & mask_000f; + const VecU16 lookup_result = vecu16_shuffle8(lookup_vec, ready_for_lookup) & mask_000f; + half_call_error_bit2_vec |= lookup_result; + const VecU16 ready_for_movemask = vecu16_slli(lookup_result, 7) | vecu16_slli(lookup_result, 14); + const Vec8thUint geno_bits = vecu16_movemask(ready_for_movemask); + genovec_alias[vidx] = geno_bits; + } + half_call_error_bit2_vec = vecu16_slli(half_call_error_bit2_vec, 5); + if (unlikely(vecu16_movemask(half_call_error_bit2_vec))) { + return kBcfParseHalfCallError; + } +#else + // todo: benchmark explicit vectorization of SSE2; I suspect that, with + // no parallel-lookup operation, it would usually be worse than this + // simple sparse-optimization. + const uint16_t* gt_iter = R_CAST(const uint16_t*, gt_main); + uintptr_t half_call_error_bit2 = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + uintptr_t geno_word = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const uint16_t phaseless_val = gt_iter[sample_idx_lowbits] & 0xfefe; + if (phaseless_val == 0x202) { + continue; + } + unsigned char first_allele_idx_p1 = phaseless_val >> 1; + unsigned char second_allele_idx_p1 = phaseless_val >> 9; + if (first_allele_idx_p1 > 3) { + first_allele_idx_p1 = 3; + } + if (second_allele_idx_p1 > 3) { + second_allele_idx_p1 = 3; + } + const uintptr_t result = biallelic_gt_lookup[first_allele_idx_p1 + second_allele_idx_p1 * 4]; + half_call_error_bit2 |= result; + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word; + gt_iter = &(gt_iter[kBitsPerWordD2]); + } + if (unlikely(half_call_error_bit2 & 4)) { + return kBcfParseHalfCallError; + } +#endif + } else { + // triploid, etc. +#ifdef USE_SSE42 + const uint32_t word_ct_m1 = (sample_ct - 1) / kBitsPerWordD2; + uint32_t loop_len = kBitsPerWordD2; +#endif + const unsigned char* gt_iter = gt_main; + uintptr_t half_call_error_bit2 = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + uintptr_t geno_word = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + // may overread 1 byte + uint32_t phaseless_val; + memcpy(&phaseless_val, gt_iter, 4); + gt_iter = &(gt_iter[value_ct]); + phaseless_val &= 0xfffefe; + if (phaseless_val == 0x810202) { + continue; + } + uintptr_t result; + if ((phaseless_val & 0x810000) != 0x810000) { + // triploid+ (or malformed) + result = 3; + } else { + unsigned char first_allele_idx_p1 = phaseless_val >> 1; + unsigned char second_allele_idx_p1 = (phaseless_val >> 9) & 0x7f; + if (first_allele_idx_p1 > 3) { + first_allele_idx_p1 = 3; + } + if (second_allele_idx_p1 > 3) { + second_allele_idx_p1 = 3; + } + result = biallelic_gt_lookup[first_allele_idx_p1 + second_allele_idx_p1 * 4]; + half_call_error_bit2 |= result; + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word; + } + if (unlikely(half_call_error_bit2 & 4)) { + return kBcfParseHalfCallError; + } + } + } + return BcfApplyGqDpFilters(bibcp, metap, record_start, genovec); +} + +static_assert(sizeof(AlleleCode) == 1, "BcfConvertMultiallelicHaploidInt8Gt() needs to be updated."); +// genovec assumed to be initialized by BcfApplyGqDpFilters() +void BcfConvertMultiallelicHaploidInt8Gt(const unsigned char* gt_main, uint32_t sample_ct, uint32_t allele_ct, uintptr_t* __restrict genovec, Halfword* __restrict patch_10_set_alias, AlleleCode** patch_10_iterp) { + const unsigned char* gt_iter = gt_main; + const uint32_t word_ct_m1 = (sample_ct - 1) / kBitsPerWordD2; + DoubleAlleleCode* patch_10_vals_alias = R_CAST(DoubleAlleleCode*, *patch_10_iterp); + DoubleAlleleCode* patch_10_iter = patch_10_vals_alias; + uint32_t loop_len = kBitsPerWordD2; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t geno_word = 0; + uint32_t patch_10_set_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const unsigned char raw_val_m2 = gt_iter[sample_idx_lowbits] - 2; + if ((!raw_val_m2) || ((gq_dp_fail_word >> (2 * sample_idx_lowbits)) & 1)) { + continue; + } + const uint32_t allele_idx = raw_val_m2 >> 1; + uintptr_t result; + if (allele_idx >= allele_ct) { + // missing and END_OF_VECTOR wind up here + result = 3; + } else { + result = 2; + // note that allele_idx == 0 is possible with malformed raw_val == 3, + // we don't handle it "correctly" but we do need to avoid blowing up + if (allele_idx >= 2) { + patch_10_set_hw |= 1U << sample_idx_lowbits; + *patch_10_iter++ = allele_idx * 0x101; + } + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word | gq_dp_fail_word; + patch_10_set_alias[widx] = patch_10_set_hw; + gt_iter = &(gt_iter[kBitsPerWordD2]); + } + *patch_10_iterp = R_CAST(AlleleCode*, patch_10_iter); +} + +static_assert(sizeof(AlleleCode) == 1, "BcfConvertMultiallelicHaploidInt8Gt() needs to be updated."); +// genovec assumed to be initialized by BcfApplyGqDpFilters() +void BcfConvertMultiallelicHaploidInt16Gt(const unsigned char* gt_main, uint32_t sample_ct, uint32_t allele_ct, uintptr_t* __restrict genovec, Halfword* __restrict patch_10_set_alias, AlleleCode** patch_10_iterp) { + const uint16_t* gt_iter = R_CAST(const uint16_t*, gt_main); + const uint32_t word_ct_m1 = (sample_ct - 1) / kBitsPerWordD2; + DoubleAlleleCode* patch_10_vals_alias = R_CAST(DoubleAlleleCode*, *patch_10_iterp); + DoubleAlleleCode* patch_10_iter = patch_10_vals_alias; + uint32_t loop_len = kBitsPerWordD2; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t geno_word = 0; + uint32_t patch_10_set_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const uint16_t raw_val_m2 = gt_iter[sample_idx_lowbits] - 2; + if ((!raw_val_m2) || ((gq_dp_fail_word >> (2 * sample_idx_lowbits)) & 1)) { + continue; + } + const uint32_t allele_idx = raw_val_m2 >> 1; + uintptr_t result; + if (allele_idx >= allele_ct) { + result = 3; + } else { + result = 2; + if (allele_idx >= 2) { + patch_10_set_hw |= 1U << sample_idx_lowbits; + *patch_10_iter++ = allele_idx * 0x101; + } + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word | gq_dp_fail_word; + patch_10_set_alias[widx] = patch_10_set_hw; + gt_iter = &(gt_iter[kBitsPerWordD2]); + } + *patch_10_iterp = R_CAST(AlleleCode*, patch_10_iter); +} + +BcfParseErr BcfConvertUnphasedMultiallelic(const BcfImportBaseContext* bibcp, const GparseReadBcfMetadata* metap, const unsigned char* record_start, uint32_t allele_ct, uint32_t* __restrict patch_01_ctp, uint32_t* __restrict patch_10_ctp, uintptr_t* __restrict genovec, uintptr_t* __restrict patch_01_set, AlleleCode* __restrict patch_01_vals, uintptr_t* __restrict patch_10_set, AlleleCode* __restrict patch_10_vals) { + const uint32_t sample_ct = bibcp->sample_ct; + const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); + ZeroWArr(sample_ctl2, genovec); + BcfParseErr bcf_parse_err = BcfApplyGqDpFilters(bibcp, metap, record_start, genovec); + if (unlikely(bcf_parse_err != kBcfParseOk)) { + return bcf_parse_err; + } + const uint32_t sample_ctl = BitCtToWordCt(sample_ct); + const uint32_t word_ct_m1 = (sample_ct - 1) / kBitsPerWordD2; + patch_01_set[sample_ctl - 1] = 0; + patch_10_set[sample_ctl - 1] = 0; + Halfword* patch_01_set_alias = R_CAST(Halfword*, patch_01_set); + Halfword* patch_10_set_alias = R_CAST(Halfword*, patch_10_set); + const VcfHalfCall halfcall_mode = bibcp->halfcall_mode; + AlleleCode* patch_01_iter = patch_01_vals; + AlleleCode* patch_10_iter = patch_10_vals; + uint32_t loop_len = kBitsPerWordD2; + const unsigned char* gt_main = &(record_start[metap->gt_vec_offset * S_CAST(uintptr_t, kBytesPerVec)]); + uint32_t value_type; + uint32_t value_ct; + ScanBcfTypeAligned(>_main, &value_type, &value_ct); + if (value_type == 1) { + // int8 + if (allele_ct >= 64) { + // don't misinterpret END_OF_VECTOR + allele_ct = 63; + } + if (value_ct == 1) { + ZeroWArr(sample_ctl, patch_01_set); + BcfConvertMultiallelicHaploidInt8Gt(gt_main, sample_ct, allele_ct, genovec, patch_10_set_alias, &patch_10_iter); + } else if (value_ct == 2) { + const uint16_t* gt_iter = R_CAST(const uint16_t*, gt_main); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t geno_word = 0; + uint32_t patch_01_set_hw = 0; + uint32_t patch_10_set_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const uint16_t phaseless_val = gt_iter[sample_idx_lowbits] & 0xfefe; + if ((phaseless_val == 0x202) || ((gq_dp_fail_word >> (2 * sample_idx_lowbits)) & 1)) { + continue; + } + const uint16_t shifted_phaseless_val = phaseless_val >> 1; + unsigned char first_allele_idx_p1 = shifted_phaseless_val; + uintptr_t result; + if (first_allele_idx_p1 > allele_ct) { + // assume end-of-vector + result = 3; + } else { + unsigned char second_allele_idx_p1 = shifted_phaseless_val >> 8; + const uint32_t cur_bit = 1U << sample_idx_lowbits; + if (second_allele_idx_p1 > allele_ct) { + // haploid + if (!first_allele_idx_p1) { + result = 3; + } else { + const unsigned char first_allele_idx = first_allele_idx_p1 - 1; + if (first_allele_idx < 2) { + result = first_allele_idx * 2; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx; + *patch_10_iter++ = first_allele_idx; + } + } + } else { + // diploid + if (second_allele_idx_p1 < first_allele_idx_p1) { + const unsigned char ucc = first_allele_idx_p1; + first_allele_idx_p1 = second_allele_idx_p1; + second_allele_idx_p1 = ucc; + } + if (!first_allele_idx_p1) { + // missing or half-call + if ((!second_allele_idx_p1) || (halfcall_mode == kVcfHalfCallMissing)) { + result = 3; + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } else { + const unsigned char second_allele_idx = second_allele_idx_p1 - 1; + if (second_allele_idx < 2) { + // kVcfHalfCallHaploid, kVcfHalfCallReference + result = second_allele_idx << halfcall_mode; + } else if (halfcall_mode == kVcfHalfCallReference) { + result = 1; + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = second_allele_idx; + *patch_10_iter++ = second_allele_idx; + } + } + } else if (first_allele_idx_p1 == 1) { + // note that we've handled the second_allele_idx_p1 == 0 and == + // 1 cases earlier + result = 1; + if (second_allele_idx_p1 > 2) { + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx_p1 - 1; + } + } else { + result = 2; + if (second_allele_idx_p1 > 2) { + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx_p1 - 1; + *patch_10_iter++ = second_allele_idx_p1 - 1; + } + } + } + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word | gq_dp_fail_word; + patch_01_set_alias[widx] = patch_01_set_hw; + patch_10_set_alias[widx] = patch_10_set_hw; + gt_iter = &(gt_iter[kBitsPerWordD2]); + } + } else { + const unsigned char* gt_iter = gt_main; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t geno_word = 0; + uint32_t patch_01_set_hw = 0; + uint32_t patch_10_set_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + // may overread 1 byte + uint32_t phaseless_val; + memcpy(&phaseless_val, gt_iter, 4); + gt_iter = &(gt_iter[value_ct]); + phaseless_val &= 0xfffefe; + if ((phaseless_val == 0x810202) || ((gq_dp_fail_word >> (2 * sample_idx_lowbits)) & 1)) { + continue; + } + uintptr_t result; + if ((phaseless_val & 0x810000) != 0x810000) { + // triploid+ (or malformed) + result = 3; + } else { + const uint16_t shifted_phaseless_val = (phaseless_val & 0xffff) >> 1; + // rest of this is identical to diploid case + // todo: try making this a static inline function (didn't start + // with that since the function would have 10+ parameters, many of + // which may not be touched on a given function call) + unsigned char first_allele_idx_p1 = shifted_phaseless_val; + if (first_allele_idx_p1 > allele_ct) { + // assume end-of-vector + result = 3; + } else { + unsigned char second_allele_idx_p1 = shifted_phaseless_val >> 8; + const uint32_t cur_bit = 1U << sample_idx_lowbits; + if (second_allele_idx_p1 > allele_ct) { + // haploid + if (!first_allele_idx_p1) { + result = 3; + } else { + const unsigned char first_allele_idx = first_allele_idx_p1 - 1; + if (first_allele_idx < 2) { + result = first_allele_idx * 2; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx; + *patch_10_iter++ = first_allele_idx; + } + } + } else { + // diploid + if (second_allele_idx_p1 < first_allele_idx_p1) { + const unsigned char ucc = first_allele_idx_p1; + first_allele_idx_p1 = second_allele_idx_p1; + second_allele_idx_p1 = ucc; + } + if (!first_allele_idx_p1) { + // missing or half-call + if ((!second_allele_idx_p1) || (halfcall_mode == kVcfHalfCallMissing)) { + result = 3; + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } else { + const unsigned char second_allele_idx = second_allele_idx_p1 - 1; + if (second_allele_idx < 2) { + // kVcfHalfCallHaploid, kVcfHalfCallReference + result = second_allele_idx << halfcall_mode; + } else if (halfcall_mode == kVcfHalfCallReference) { + result = 1; + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = second_allele_idx; + *patch_10_iter++ = second_allele_idx; + } + } + } else if (first_allele_idx_p1 == 1) { + result = 1; + if (second_allele_idx_p1 > 2) { + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx_p1 - 1; + } + } else { + result = 2; + if (second_allele_idx_p1 > 2) { + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx_p1 - 1; + *patch_10_iter++ = second_allele_idx_p1 - 1; + } + } + } + } + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word | gq_dp_fail_word; + patch_01_set_alias[widx] = patch_01_set_hw; + patch_10_set_alias[widx] = patch_10_set_hw; + } + } + } else if (likely(value_type == 2)) { + // int16 + if (value_ct == 1) { + BcfConvertMultiallelicHaploidInt16Gt(gt_main, sample_ct, allele_ct, genovec, patch_10_set_alias, &patch_10_iter); + } else if (value_ct == 2) { + const uint32_t* gt_iter = R_CAST(const uint32_t*, gt_main); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t geno_word = 0; + uint32_t patch_01_set_hw = 0; + uint32_t patch_10_set_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const uint32_t phaseless_val = gt_iter[sample_idx_lowbits] & 0xfffefffeU; + if ((phaseless_val == 0x20002) || ((gq_dp_fail_word >> (2 * sample_idx_lowbits)) & 1)) { + continue; + } + const uint32_t shifted_phaseless_val = phaseless_val >> 1; + uint16_t first_allele_idx_p1 = shifted_phaseless_val; + uintptr_t result; + if (first_allele_idx_p1 > allele_ct) { + // assume end-of-vector + result = 3; + } else { + uint16_t second_allele_idx_p1 = shifted_phaseless_val >> 16; + const uint32_t cur_bit = 1U << sample_idx_lowbits; + if (second_allele_idx_p1 > allele_ct) { + // haploid + if (!first_allele_idx_p1) { + result = 3; + } else { + const uint16_t first_allele_idx = first_allele_idx_p1 - 1; + if (first_allele_idx < 2) { + result = first_allele_idx * 2; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx; + *patch_10_iter++ = first_allele_idx; + } + } + } else { + // diploid + if (second_allele_idx_p1 < first_allele_idx_p1) { + const uint16_t usii = first_allele_idx_p1; + first_allele_idx_p1 = second_allele_idx_p1; + second_allele_idx_p1 = usii; + } + if (!first_allele_idx_p1) { + // missing or half-call + if ((!second_allele_idx_p1) || (halfcall_mode == kVcfHalfCallMissing)) { + result = 3; + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } else { + const uint16_t second_allele_idx = second_allele_idx_p1 - 1; + if (second_allele_idx < 2) { + // kVcfHalfCallHaploid, kVcfHalfCallReference + result = second_allele_idx << halfcall_mode; + } else if (halfcall_mode == kVcfHalfCallReference) { + result = 1; + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = second_allele_idx; + *patch_10_iter++ = second_allele_idx; + } + } + } else if (first_allele_idx_p1 == 1) { + // note that we've handled the second_allele_idx_p1 == 0 and == + // 1 cases earlier + result = 1; + if (second_allele_idx_p1 > 2) { + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx_p1 - 1; + } + } else { + result = 2; + if (second_allele_idx_p1 > 2) { + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx_p1 - 1; + *patch_10_iter++ = second_allele_idx_p1 - 1; + } + } + } + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word | gq_dp_fail_word; + patch_01_set_alias[widx] = patch_01_set_hw; + patch_10_set_alias[widx] = patch_10_set_hw; + gt_iter = &(gt_iter[kBitsPerWordD2]); + } + } else { + const uint16_t* gt_iter = R_CAST(const uint16_t*, gt_main); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t geno_word = 0; + uint32_t patch_01_set_hw = 0; + uint32_t patch_10_set_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + // may overread 2 bytes + uint64_t phaseless_val; + memcpy(&phaseless_val, gt_iter, 8); + gt_iter = &(gt_iter[value_ct]); + phaseless_val &= 0xfffffffefffeLLU; + if ((phaseless_val == 0x800100020002LLU) || ((gq_dp_fail_word >> (2 * sample_idx_lowbits)) & 1)) { + continue; + } + uintptr_t result; + if ((phaseless_val & 0x800100000000LLU) != 0x800100000000LLU) { + // triploid+ (or malformed) + result = 3; + } else { + const uint32_t shifted_phaseless_val = S_CAST(uint32_t, phaseless_val) >> 1; + // rest of this is identical to diploid case + uint16_t first_allele_idx_p1 = shifted_phaseless_val; + if (first_allele_idx_p1 > allele_ct) { + // assume end-of-vector + result = 3; + } else { + uint16_t second_allele_idx_p1 = shifted_phaseless_val >> 16; + const uint32_t cur_bit = 1U << sample_idx_lowbits; + if (second_allele_idx_p1 > allele_ct) { + // haploid + if (!first_allele_idx_p1) { + result = 3; + } else { + const uint16_t first_allele_idx = first_allele_idx_p1 - 1; + if (first_allele_idx < 2) { + result = first_allele_idx * 2; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx; + *patch_10_iter++ = first_allele_idx; + } + } + } else { + // diploid + if (second_allele_idx_p1 < first_allele_idx_p1) { + const uint16_t usii = first_allele_idx_p1; + first_allele_idx_p1 = second_allele_idx_p1; + second_allele_idx_p1 = usii; + } + if (!first_allele_idx_p1) { + // missing or half-call + if ((!second_allele_idx_p1) || (halfcall_mode == kVcfHalfCallMissing)) { + result = 3; + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } else { + const uint16_t second_allele_idx = second_allele_idx_p1 - 1; + if (second_allele_idx < 2) { + // kVcfHalfCallHaploid, kVcfHalfCallReference + result = second_allele_idx << halfcall_mode; + } else if (halfcall_mode == kVcfHalfCallReference) { + result = 1; + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = second_allele_idx; + *patch_10_iter++ = second_allele_idx; + } + } + } else if (first_allele_idx_p1 == 1) { + result = 1; + if (second_allele_idx_p1 > 2) { + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx_p1 - 1; + } + } else { + result = 2; + if (second_allele_idx_p1 > 2) { + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx_p1 - 1; + *patch_10_iter++ = second_allele_idx_p1 - 1; + } + } + } + } + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word | gq_dp_fail_word; + patch_01_set_alias[widx] = patch_01_set_hw; + patch_10_set_alias[widx] = patch_10_set_hw; + } + } + } else { + return (value_type == 3)? kBcfParseWideGt : kBcfParseMalformedGeneric; + } + *patch_01_ctp = patch_01_iter - patch_01_vals; + *patch_10_ctp = S_CAST(uintptr_t, patch_10_iter - patch_10_vals) / 2; + return kBcfParseOk; +} + +// GT present, phase matters, dosage/HDS not present. +BcfParseErr BcfConvertPhasedBiallelic(const BcfImportBaseContext* bibcp, const GparseReadBcfMetadata* metap, unsigned char* record_start, uintptr_t* __restrict genovec, uintptr_t* __restrict phasepresent, uintptr_t* __restrict phaseinfo) { + unsigned char* gt_type_start = &(record_start[metap->gt_vec_offset * S_CAST(uintptr_t, kBytesPerVec)]); + const unsigned char* gt_main = gt_type_start; + // this was partially validated in first pass + uint32_t value_type; + uint32_t value_ct; + ScanBcfTypeAligned(>_main, &value_type, &value_ct); + if (unlikely(value_type != 1)) { + return (value_type <= 3)? kBcfParseWideGt : kBcfParseMalformedGeneric; + } + const uint32_t sample_ct = bibcp->sample_ct; + const uint32_t sample_ctl = BitCtToWordCt(sample_ct); + if (value_ct == 1) { + ZeroWArr(sample_ctl, phasepresent); + BcfConvertBiallelicHaploidGt(gt_main, sample_ct, genovec); + } else { + phasepresent[sample_ctl - 1] = 0; + phaseinfo[sample_ctl - 1] = 0; + const unsigned char* biallelic_gt_lookup = bibcp->biallelic_gt_lookup; +#ifdef USE_SSE42 + const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); + genovec[sample_ctl2 - 1] = 0; +#else + const uint32_t word_ct_m1 = (sample_ct - 1) / kBitsPerWordD2; + uint32_t loop_len = kBitsPerWordD2; +#endif + if (value_ct == 2) { + // Regular diploid. + // Additional requirements beyond unphased case: + // - Extract original bit 8 (low bit of second genotype value). No need + // to do anything else (verify non-END_OF_VECTOR or het status) with + // it, here, that's taken care of after the GQ/DP filter. + // - Extract original bit 9 (since the variant is biallelic, this bit + // should only be set when the second genotype value is 0, i.e. if the + // genotype is a phased het, it's 1|0). +#ifdef USE_SSE42 + const uint32_t vec_ct = DivUp(sample_ct, kInt16PerVec); + const uint32_t inv_remainder = (-sample_ct) & (kInt16PerVec - 1); + if (inv_remainder) { + // Set all past-the-end bytes to 2. This causes trailing genovec and + // phasepresent bits to be zero, and doesn't create a + // half_call_error_bit2_vec false positive. + memset(&(gt_type_start[kBytesPerVec + sample_ct * 2]), 2, 2 * inv_remainder); + } + const VecU16* gt_alias = R_CAST(const VecU16*, gt_main); + Vec8thUint* genovec_alias = R_CAST(Vec8thUint*, genovec); + Vec16thUint* phasepresent_alias = R_CAST(Vec16thUint*, phasepresent); + Vec16thUint* phaseinfo_alias = R_CAST(Vec16thUint*, phaseinfo); + + const VecU16 hibit_mask = vecu16_set1(0x7f7f); + const VecU16 three = vecu16_set1(0x303); + const VecU16 mask_000f = vecu16_set1(0xf); +# ifndef USE_AVX2 + const VecU16 gather_even = vecu16_setr8(0, 2, 4, 6, 8, 10, 12, 14, + -1, -1, -1, -1, -1, -1, -1, -1); +# endif + const VecU16 lookup_vec = vecu16_loadu(biallelic_gt_lookup); + VecU16 half_call_error_bit2_vec = vecu16_setzero(); + for (uint32_t vidx = 0; vidx != vec_ct; ++vidx) { + const VecU16 bcf_bytes = vecu16_loadu(&(gt_alias[vidx])); + const VecU16 shifted_unmasked_bytes = vecu16_srli(bcf_bytes, 1); + const VecU16 bit9s_at_bit7 = vecu16_srli(bcf_bytes, 2); +# ifdef USE_AVX2 + const Vec16thUint cur_phasepresent = _pext_u32(vecu16_movemask(shifted_unmasked_bytes), 0x55555555); + const Vec16thUint cur_phaseinfo = _pext_u32(vecu16_movemask(bit9s_at_bit7), 0x55555555); +# else + const VecU16 phasepresent_vec = vecu16_shuffle8(shifted_unmasked_bytes, gather_even); + const VecU16 phaseinfo_vec = vecu16_shuffle8(bit9s_at_bit7, gather_even); + const Vec16thUint cur_phasepresent = vecu16_movemask(phasepresent_vec); + const Vec16thUint cur_phaseinfo = vecu16_movemask(phaseinfo_vec); +# endif + phasepresent_alias[vidx] = cur_phasepresent; + phaseinfo_alias[vidx] = cur_phaseinfo; + + const VecU16 shifted_bytes = shifted_unmasked_bytes & hibit_mask; + const VecU16 capped_bytes = vecu16_min8(shifted_bytes, three); + const VecU16 ready_for_lookup = (capped_bytes | vecu16_srli(capped_bytes, 6)) & mask_000f; + const VecU16 lookup_result = vecu16_shuffle8(lookup_vec, ready_for_lookup) & mask_000f; + half_call_error_bit2_vec |= lookup_result; + const VecU16 ready_for_movemask = vecu16_slli(lookup_result, 7) | vecu16_slli(lookup_result, 14); + const Vec8thUint geno_bits = vecu16_movemask(ready_for_movemask); + genovec_alias[vidx] = geno_bits; + } + half_call_error_bit2_vec = vecu16_slli(half_call_error_bit2_vec, 5); + if (unlikely(vecu16_movemask(half_call_error_bit2_vec))) { + return kBcfParseHalfCallError; + } +#else + // todo: benchmark explicit vectorization of SSE2 + const uint16_t* gt_iter = R_CAST(const uint16_t*, gt_main); + Halfword* phasepresent_alias = R_CAST(Halfword*, phasepresent); + Halfword* phaseinfo_alias = R_CAST(Halfword*, phaseinfo); + uintptr_t half_call_error_bit2 = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + uintptr_t geno_word = 0; + uintptr_t phasepresent_hw_shifted = 0; + Halfword phaseinfo_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const uintptr_t raw_val = gt_iter[sample_idx_lowbits]; + if ((raw_val & 0xfefe) == 0x202) { + continue; + } + const uintptr_t bit_8 = raw_val & 0x100; + phasepresent_hw_shifted |= bit_8 << sample_idx_lowbits; + unsigned char second_allele_idx_p1 = raw_val >> 9; + phaseinfo_hw |= (second_allele_idx_p1 & 1) << sample_idx_lowbits; + unsigned char first_allele_idx_p1 = (raw_val ^ bit_8) >> 1; + if (first_allele_idx_p1 > 3) { + first_allele_idx_p1 = 3; + } + if (second_allele_idx_p1 > 3) { + second_allele_idx_p1 = 3; + } + const uintptr_t result = biallelic_gt_lookup[first_allele_idx_p1 + second_allele_idx_p1 * 4]; + half_call_error_bit2 |= result; + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word; + phasepresent_alias[widx] = phasepresent_hw_shifted >> 8; + phaseinfo_alias[widx] = phaseinfo_hw; + gt_iter = &(gt_iter[kBitsPerWordD2]); + } + if (unlikely(half_call_error_bit2 & 4)) { + return kBcfParseHalfCallError; + } +#endif + } else { + // triploid, etc. + Halfword* phasepresent_alias = R_CAST(Halfword*, phasepresent); + Halfword* phaseinfo_alias = R_CAST(Halfword*, phaseinfo); +#ifdef USE_SSE42 + const uint32_t word_ct_m1 = (sample_ct - 1) / kBitsPerWordD2; + uint32_t loop_len = kBitsPerWordD2; +#endif + const unsigned char* gt_iter = gt_main; + uintptr_t half_call_error_bit2 = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + uintptr_t geno_word = 0; + uintptr_t phasepresent_hw_shifted = 0; + Halfword phaseinfo_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + // may overread 1 byte + uint32_t raw_val; + memcpy(&raw_val, gt_iter, 4); + raw_val &= 0xffffff; + gt_iter = &(gt_iter[value_ct]); + if ((raw_val & 0xfffefe) == 0x810202) { + continue; + } + uintptr_t result; + if ((raw_val & 0x810000) != 0x810000) { + // triploid (or malformed) + result = 3; + } else { + const uintptr_t bit_8 = raw_val & 0x100; + phasepresent_hw_shifted |= bit_8 << sample_idx_lowbits; + unsigned char second_allele_idx_p1 = (raw_val >> 9) & 0x7f; + phaseinfo_hw |= (second_allele_idx_p1 & 1) << sample_idx_lowbits; + unsigned char first_allele_idx_p1 = (raw_val ^ bit_8) >> 1; + if (first_allele_idx_p1 > 3) { + first_allele_idx_p1 = 3; + } + if (second_allele_idx_p1 > 3) { + second_allele_idx_p1 = 3; + } + result = biallelic_gt_lookup[first_allele_idx_p1 + second_allele_idx_p1 * 4]; + half_call_error_bit2 |= result; + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word; + phasepresent_alias[widx] = phasepresent_hw_shifted >> 8; + phaseinfo_alias[widx] = phaseinfo_hw; + } + if (unlikely(half_call_error_bit2 & 4)) { + return kBcfParseHalfCallError; + } + } + } + BcfParseErr bcf_parse_err = BcfApplyGqDpFilters(bibcp, metap, record_start, genovec); + if (unlikely(bcf_parse_err != kBcfParseOk)) { + return bcf_parse_err; + } + if (value_ct != 1) { + const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); + Halfword* phasepresent_alias = R_CAST(Halfword*, phasepresent); + // mask out all phasepresent bits which don't correspond to hets. + for (uint32_t widx = 0; widx != sample_ctl2; ++widx) { + phasepresent_alias[widx] &= Pack01ToHalfword(genovec[widx]); + } + } + return kBcfParseOk; +} + +BcfParseErr BcfConvertPhasedMultiallelic(const BcfImportBaseContext* bibcp, const GparseReadBcfMetadata* metap, unsigned char* record_start, uint32_t allele_ct, uint32_t* __restrict patch_01_ctp, uint32_t* __restrict patch_10_ctp, uintptr_t* __restrict genovec, uintptr_t* __restrict patch_01_set, AlleleCode* __restrict patch_01_vals, uintptr_t* __restrict patch_10_set, AlleleCode* __restrict patch_10_vals, uintptr_t* __restrict phasepresent, uintptr_t* __restrict phaseinfo) { + // yes, there's lots of duplication with BcfConvertUnphasedMultiallelic... + const uint32_t sample_ct = bibcp->sample_ct; + const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); + ZeroWArr(sample_ctl2, genovec); + BcfParseErr bcf_parse_err = BcfApplyGqDpFilters(bibcp, metap, record_start, genovec); + if (unlikely(bcf_parse_err != kBcfParseOk)) { + return bcf_parse_err; + } + const uint32_t sample_ctl = BitCtToWordCt(sample_ct); + const uint32_t word_ct_m1 = (sample_ct - 1) / kBitsPerWordD2; + patch_01_set[sample_ctl - 1] = 0; + patch_10_set[sample_ctl - 1] = 0; + Halfword* patch_01_set_alias = R_CAST(Halfword*, patch_01_set); + Halfword* patch_10_set_alias = R_CAST(Halfword*, patch_10_set); + phasepresent[sample_ctl - 1] = 0; + Halfword* phasepresent_alias = R_CAST(Halfword*, phasepresent); + Halfword* phaseinfo_alias = R_CAST(Halfword*, phaseinfo); + const VcfHalfCall halfcall_mode = bibcp->halfcall_mode; + AlleleCode* patch_01_iter = patch_01_vals; + AlleleCode* patch_10_iter = patch_10_vals; + uint32_t loop_len = kBitsPerWordD2; + const unsigned char* gt_main = &(record_start[metap->gt_vec_offset * S_CAST(uintptr_t, kBytesPerVec)]); + uint32_t value_type; + uint32_t value_ct; + ScanBcfTypeAligned(>_main, &value_type, &value_ct); + if (value_type == 1) { + // int8 + if (allele_ct >= 64) { + // don't misinterpret END_OF_VECTOR + allele_ct = 63; + } + if (value_ct == 1) { + ZeroWArr(sample_ctl, patch_01_set); + ZeroWArr(sample_ctl, phasepresent); + // phaseinfo doesn't matter + BcfConvertMultiallelicHaploidInt8Gt(gt_main, sample_ct, allele_ct, genovec, patch_10_set_alias, &patch_10_iter); + } else if (value_ct == 2) { + const uint16_t* gt_iter = R_CAST(const uint16_t*, gt_main); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t geno_word = 0; + uint32_t patch_01_set_hw = 0; + uint32_t patch_10_set_hw = 0; + uint32_t phasepresent_hw = 0; + uint32_t phaseinfo_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const uint16_t raw_val = gt_iter[sample_idx_lowbits]; + const uint16_t phaseless_val = raw_val & 0xfefe; + if ((phaseless_val == 0x202) || ((gq_dp_fail_word >> (2 * sample_idx_lowbits)) & 1)) { + continue; + } + const uint16_t shifted_phaseless_val = phaseless_val >> 1; + unsigned char first_allele_idx_p1 = shifted_phaseless_val; + uintptr_t result; + if (first_allele_idx_p1 > allele_ct) { + // assume end-of-vector + result = 3; + } else { + unsigned char second_allele_idx_p1 = shifted_phaseless_val >> 8; + const uint32_t cur_bit = 1U << sample_idx_lowbits; + if (second_allele_idx_p1 > allele_ct) { + // haploid + if (!first_allele_idx_p1) { + result = 3; + } else { + const unsigned char first_allele_idx = first_allele_idx_p1 - 1; + if (first_allele_idx < 2) { + result = first_allele_idx * 2; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx; + *patch_10_iter++ = first_allele_idx; + } + } + } else { + // diploid + if (second_allele_idx_p1 < first_allele_idx_p1) { + const unsigned char ucc = first_allele_idx_p1; + first_allele_idx_p1 = second_allele_idx_p1; + second_allele_idx_p1 = ucc; + phaseinfo_hw |= cur_bit; + } + if (!first_allele_idx_p1) { + // missing or half-call + if ((!second_allele_idx_p1) || (halfcall_mode == kVcfHalfCallMissing)) { + result = 3; + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } else { + const unsigned char second_allele_idx = second_allele_idx_p1 - 1; + if (second_allele_idx < 2) { + // kVcfHalfCallHaploid, kVcfHalfCallReference + result = second_allele_idx << halfcall_mode; + } else if (halfcall_mode == kVcfHalfCallReference) { + result = 1; + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = second_allele_idx; + *patch_10_iter++ = second_allele_idx; + } + if ((raw_val & 0x100) && (result == 1)) { + phasepresent_hw |= cur_bit; + } + } + } else { + if ((raw_val & 0x100) && (first_allele_idx_p1 != second_allele_idx_p1)) { + phasepresent_hw |= cur_bit; + } + if (first_allele_idx_p1 == 1) { + // note that we've handled the second_allele_idx_p1 == 0 and + // == 1 cases earlier + result = 1; + if (second_allele_idx_p1 > 2) { + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx_p1 - 1; + } + } else { + result = 2; + if (second_allele_idx_p1 > 2) { + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx_p1 - 1; + *patch_10_iter++ = second_allele_idx_p1 - 1; + } + } + } + } + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word | gq_dp_fail_word; + patch_01_set_alias[widx] = patch_01_set_hw; + patch_10_set_alias[widx] = patch_10_set_hw; + phasepresent_alias[widx] = phasepresent_hw; + phaseinfo_alias[widx] = phaseinfo_hw; + gt_iter = &(gt_iter[kBitsPerWordD2]); + } + } else { + const unsigned char* gt_iter = gt_main; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t geno_word = 0; + uint32_t patch_01_set_hw = 0; + uint32_t patch_10_set_hw = 0; + uint32_t phasepresent_hw = 0; + uint32_t phaseinfo_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + // may overread 1 byte + uint32_t raw_val; + memcpy(&raw_val, gt_iter, 4); + gt_iter = &(gt_iter[value_ct]); + const uint32_t phaseless_val = raw_val & 0xfffefe; + if ((phaseless_val == 0x810202) || ((gq_dp_fail_word >> (2 * sample_idx_lowbits)) & 1)) { + continue; + } + uintptr_t result; + if ((phaseless_val & 0x810000) != 0x810000) { + // triploid+ (or malformed) + result = 3; + } else { + const uint16_t shifted_phaseless_val = (phaseless_val & 0xffff) >> 1; + // rest of this is identical to diploid case + unsigned char first_allele_idx_p1 = shifted_phaseless_val; + if (first_allele_idx_p1 > allele_ct) { + // assume end-of-vector + result = 3; + } else { + unsigned char second_allele_idx_p1 = shifted_phaseless_val >> 8; + const uint32_t cur_bit = 1U << sample_idx_lowbits; + if (second_allele_idx_p1 > allele_ct) { + // haploid + if (!first_allele_idx_p1) { + result = 3; + } else { + const unsigned char first_allele_idx = first_allele_idx_p1 - 1; + if (first_allele_idx < 2) { + result = first_allele_idx * 2; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx; + *patch_10_iter++ = first_allele_idx; + } + } + } else { + // diploid + if (second_allele_idx_p1 < first_allele_idx_p1) { + const unsigned char ucc = first_allele_idx_p1; + first_allele_idx_p1 = second_allele_idx_p1; + second_allele_idx_p1 = ucc; + phaseinfo_hw |= cur_bit; + } + if (!first_allele_idx_p1) { + // missing or half-call + if ((!second_allele_idx_p1) || (halfcall_mode == kVcfHalfCallMissing)) { + result = 3; + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } else { + const unsigned char second_allele_idx = second_allele_idx_p1 - 1; + if (second_allele_idx < 2) { + // kVcfHalfCallHaploid, kVcfHalfCallReference + result = second_allele_idx << halfcall_mode; + } else if (halfcall_mode == kVcfHalfCallReference) { + result = 1; + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = second_allele_idx; + *patch_10_iter++ = second_allele_idx; + } + if ((raw_val & 0x100) && (result == 1)) { + phasepresent_hw |= cur_bit; + } + } + } else { + if ((raw_val & 0x100) && (first_allele_idx_p1 != second_allele_idx_p1)) { + phasepresent_hw |= cur_bit; + } + if (first_allele_idx_p1 == 1) { + result = 1; + if (second_allele_idx_p1 > 2) { + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx_p1 - 1; + } + } else { + result = 2; + if (second_allele_idx_p1 > 2) { + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx_p1 - 1; + *patch_10_iter++ = second_allele_idx_p1 - 1; + } + } + } + } + } + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word | gq_dp_fail_word; + patch_01_set_alias[widx] = patch_01_set_hw; + patch_10_set_alias[widx] = patch_10_set_hw; + phasepresent_alias[widx] = phasepresent_hw; + phaseinfo_alias[widx] = phaseinfo_hw; + } + } + } else if (likely(value_type == 2)) { + // int16 + if (value_ct == 1) { + ZeroWArr(sample_ctl, patch_01_set); + ZeroWArr(sample_ctl, phasepresent); + // phaseinfo doesn't matter + BcfConvertMultiallelicHaploidInt16Gt(gt_main, sample_ct, allele_ct, genovec, patch_10_set_alias, &patch_10_iter); + } else if (value_ct == 2) { + const uint32_t* gt_iter = R_CAST(const uint32_t*, gt_main); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t geno_word = 0; + uint32_t patch_01_set_hw = 0; + uint32_t patch_10_set_hw = 0; + uint32_t phasepresent_hw = 0; + uint32_t phaseinfo_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + const uint32_t raw_val = gt_iter[sample_idx_lowbits]; + const uint32_t phaseless_val = raw_val & 0xfffefffeU; + if ((phaseless_val == 0x20002) || ((gq_dp_fail_word >> (2 * sample_idx_lowbits)) & 1)) { + continue; + } + const uint32_t shifted_phaseless_val = phaseless_val >> 1; + uint16_t first_allele_idx_p1 = shifted_phaseless_val; + uintptr_t result; + if (first_allele_idx_p1 > allele_ct) { + // assume end-of-vector + result = 3; + } else { + uint16_t second_allele_idx_p1 = shifted_phaseless_val >> 16; + const uint32_t cur_bit = 1U << sample_idx_lowbits; + if (second_allele_idx_p1 > allele_ct) { + // haploid + if (!first_allele_idx_p1) { + result = 3; + } else { + const uint16_t first_allele_idx = first_allele_idx_p1 - 1; + if (first_allele_idx < 2) { + result = first_allele_idx * 2; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx; + *patch_10_iter++ = first_allele_idx; + } + } + } else { + // diploid + if (second_allele_idx_p1 < first_allele_idx_p1) { + const uint16_t usii = first_allele_idx_p1; + first_allele_idx_p1 = second_allele_idx_p1; + second_allele_idx_p1 = usii; + phaseinfo_hw |= cur_bit; + } + if (!first_allele_idx_p1) { + // missing or half-call + if ((!second_allele_idx_p1) || (halfcall_mode == kVcfHalfCallMissing)) { + result = 3; + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } else { + const uint16_t second_allele_idx = second_allele_idx_p1 - 1; + if (second_allele_idx < 2) { + // kVcfHalfCallHaploid, kVcfHalfCallReference + result = second_allele_idx << halfcall_mode; + } else if (halfcall_mode == kVcfHalfCallReference) { + result = 1; + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = second_allele_idx; + *patch_10_iter++ = second_allele_idx; + } + if ((raw_val & 0x10000) && (result == 1)) { + phasepresent_hw |= cur_bit; + } + } + } else { + if ((raw_val & 0x10000) && (first_allele_idx_p1 != second_allele_idx_p1)) { + phasepresent_hw |= cur_bit; + } + if (first_allele_idx_p1 == 1) { + // note that we've handled the second_allele_idx_p1 == 0 and + // == 1 cases earlier + result = 1; + if (second_allele_idx_p1 > 2) { + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx_p1 - 1; + } + } else { + result = 2; + if (second_allele_idx_p1 > 2) { + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx_p1 - 1; + *patch_10_iter++ = second_allele_idx_p1 - 1; + } + } + } + } + } + geno_word |= result << (2 * sample_idx_lowbits); + } + genovec[widx] = geno_word | gq_dp_fail_word; + patch_01_set_alias[widx] = patch_01_set_hw; + patch_10_set_alias[widx] = patch_10_set_hw; + phasepresent_alias[widx] = phasepresent_hw; + phaseinfo_alias[widx] = phaseinfo_hw; + gt_iter = &(gt_iter[kBitsPerWordD2]); + } + } else { + const uint16_t* gt_iter = R_CAST(const uint16_t*, gt_main); + for (uint32_t widx = 0; ; ++widx) { + if (widx >= word_ct_m1) { + if (widx > word_ct_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t geno_word = 0; + uint32_t patch_01_set_hw = 0; + uint32_t patch_10_set_hw = 0; + uint32_t phasepresent_hw = 0; + uint32_t phaseinfo_hw = 0; + for (uint32_t sample_idx_lowbits = 0; sample_idx_lowbits != loop_len; ++sample_idx_lowbits) { + // may overread 2 bytes + uint64_t raw_val; + memcpy(&raw_val, gt_iter, 8); + gt_iter = &(gt_iter[value_ct]); + const uint64_t phaseless_val = raw_val & 0xfffffffefffeLLU; + if ((phaseless_val == 0x800100020002LLU) || ((gq_dp_fail_word >> (2 * sample_idx_lowbits)) & 1)) { + continue; + } + uintptr_t result; + if ((phaseless_val & 0x800100000000LLU) != 0x800100000000LLU) { + // triploid+ (or malformed) + result = 3; + } else { + const uint32_t shifted_phaseless_val = S_CAST(uint32_t, phaseless_val) >> 1; + // rest of this is identical to diploid case + uint16_t first_allele_idx_p1 = shifted_phaseless_val; + if (first_allele_idx_p1 > allele_ct) { + // assume end-of-vector + result = 3; + } else { + uint16_t second_allele_idx_p1 = shifted_phaseless_val >> 16; + const uint32_t cur_bit = 1U << sample_idx_lowbits; + if (second_allele_idx_p1 > allele_ct) { + // haploid + if (!first_allele_idx_p1) { + result = 3; + } else { + const uint16_t first_allele_idx = first_allele_idx_p1 - 1; + if (first_allele_idx < 2) { + result = first_allele_idx * 2; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx; + *patch_10_iter++ = first_allele_idx; + } + } + } else { + // diploid + if (second_allele_idx_p1 < first_allele_idx_p1) { + const uint16_t usii = first_allele_idx_p1; + first_allele_idx_p1 = second_allele_idx_p1; + second_allele_idx_p1 = usii; + phaseinfo_hw |= cur_bit; + } + if (!first_allele_idx_p1) { + // missing or half-call + if ((!second_allele_idx_p1) || (halfcall_mode == kVcfHalfCallMissing)) { + result = 3; + } else if (unlikely(halfcall_mode == kVcfHalfCallError)) { + return kBcfParseHalfCallError; + } else { + const uint16_t second_allele_idx = second_allele_idx_p1 - 1; + if (second_allele_idx < 2) { + // kVcfHalfCallHaploid, kVcfHalfCallReference + result = second_allele_idx << halfcall_mode; + } else if (halfcall_mode == kVcfHalfCallReference) { + result = 1; + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx; + } else { + result = 2; + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = second_allele_idx; + *patch_10_iter++ = second_allele_idx; + } + if ((raw_val & 0x10000) && (result == 1)) { + phasepresent_hw |= cur_bit; + } + } + } else { + if ((raw_val & 0x10000) && (first_allele_idx_p1 != second_allele_idx_p1)) { + phasepresent_hw |= cur_bit; + } + if (first_allele_idx_p1 == 1) { + result = 1; + if (second_allele_idx_p1 > 2) { + patch_01_set_hw |= cur_bit; + *patch_01_iter++ = second_allele_idx_p1 - 1; + } + } else { + result = 2; + if (second_allele_idx_p1 > 2) { + patch_10_set_hw |= cur_bit; + *patch_10_iter++ = first_allele_idx_p1 - 1; + *patch_10_iter++ = second_allele_idx_p1 - 1; + } + } + } + } + } + } + } + genovec[widx] = geno_word | gq_dp_fail_word; + patch_01_set_alias[widx] = patch_01_set_hw; + patch_10_set_alias[widx] = patch_10_set_hw; + phasepresent_alias[widx] = phasepresent_hw; + phaseinfo_alias[widx] = phaseinfo_hw; + } + } + } else { + return (value_type == 3)? kBcfParseWideGt : kBcfParseMalformedGeneric; + } + *patch_01_ctp = patch_01_iter - patch_01_vals; + *patch_10_ctp = S_CAST(uintptr_t, patch_10_iter - patch_10_vals) / 2; + return kBcfParseOk; +} + +BcfParseErr BcfConvertPhasedBiallelicDosage(const BcfImportContext* bicp, const GparseReadBcfMetadata* metap, unsigned char* record_start, uintptr_t* __restrict genovec, uintptr_t* __restrict phasepresent, uintptr_t* __restrict phaseinfo, uintptr_t* __restrict dosage_present, uintptr_t* __restrict dphase_present, Dosage** dosage_main_iter_ptr, SDosage** dphase_delta_iter_ptr) { + // See VcfConvertPhasedBiallelicDosageLine(). + const BcfImportBaseContext* bibcp = &(bicp->bibc); + const uint32_t sample_ct = bibcp->sample_ct; + const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); + ZeroWArr(sample_ctl2, genovec); + BcfParseErr bcf_parse_err = BcfApplyGqDpFilters(bibcp, metap, record_start, genovec); + if (unlikely(bcf_parse_err != kBcfParseOk)) { + return bcf_parse_err; + } + const float* hds_main = nullptr; + uint32_t hds_value_ct = 0; + if (metap->hds_vec_offset != UINT32_MAX) { + const unsigned char* hds_main_raw = &(record_start[metap->hds_vec_offset * S_CAST(uintptr_t, kBytesPerVec)]); + uint32_t hds_value_type; + ScanBcfTypeAligned(&hds_main_raw, &hds_value_type, &hds_value_ct); + if (unlikely(hds_value_type != 5)) { + return kBcfParseNonfloatDosage; + } + hds_main = R_CAST(const float*, hds_main_raw); + } + const float* dosage_main = nullptr; + uint32_t dosage_value_ct = 0; + if (metap->dosage_vec_offset != UINT32_MAX) { + const unsigned char* dosage_main_raw = &(record_start[metap->dosage_vec_offset * S_CAST(uintptr_t, kBytesPerVec)]); + uint32_t dosage_value_type; + ScanBcfTypeAligned(&dosage_main_raw, &dosage_value_type, &dosage_value_ct); + if (unlikely(dosage_value_type != 5)) { + return kBcfParseNonfloatDosage; + } + dosage_main = R_CAST(const float*, dosage_main_raw); + } + const unsigned char* gt_main = nullptr; + uint32_t gt_value_type = 0; + uint32_t gt_value_ct = 0; + if (metap->gt_vec_offset != UINT32_MAX) { + gt_main = &(record_start[metap->gt_vec_offset * S_CAST(uintptr_t, kBytesPerVec)]); + ScanBcfTypeAligned(>_main, >_value_type, >_value_ct); + if (unlikely(gt_value_type > 1)) { + return kBcfParseWideGt; + } + } + + const uint32_t sample_ctl = BitCtToWordCt(sample_ct); + phasepresent[sample_ctl - 1] = 0; + dosage_present[sample_ctl - 1] = 0; + dphase_present[sample_ctl - 1] = 0; + Halfword* phasepresent_alias = R_CAST(Halfword*, phasepresent); + Halfword* phaseinfo_alias = R_CAST(Halfword*, phaseinfo); + Halfword* dosage_present_alias = R_CAST(Halfword*, dosage_present); + Halfword* dphase_present_alias = R_CAST(Halfword*, dphase_present); + Dosage* dosage_main_iter = *dosage_main_iter_ptr; + SDosage* dphase_delta_iter = *dphase_delta_iter_ptr; + const unsigned char* biallelic_gt_lookup = bibcp->biallelic_gt_lookup; + const uint32_t sample_ctl2_m1 = sample_ctl2 - 1; + const uint32_t dosage_is_gp = bicp->dosage_is_gp; + const uint32_t dosage_erase_halfdist = bicp->dosage_erase_halfdist; + const uint32_t dphase_erase_halfdist = dosage_erase_halfdist + kDosage4th; + const double import_dosage_certainty = bicp->import_dosage_certainty; + uint32_t loop_len = kBitsPerWordD2; + uint16_t gt_raw = 0; + uint32_t is_haploid_or_0ploid = (gt_value_ct == 1); + uintptr_t half_call_error_bit2 = 0; + uint32_t sample_idx = 0; + for (uint32_t widx = 0; ; ++widx) { + if (widx >= sample_ctl2_m1) { + if (widx > sample_ctl2_m1) { + break; + } + loop_len = ModNz(sample_ct, kBitsPerWordD2); + } + const uint32_t sample_idx_stop = sample_idx + loop_len; + uintptr_t gq_dp_fail_word = genovec[widx]; + uintptr_t genovec_word = 0; + uint32_t phasepresent_hw = 0; + uint32_t phaseinfo_hw = 0; + uint32_t dosage_present_hw = 0; + uint32_t dphase_present_hw = 0; + for (; sample_idx != sample_idx_stop; ++sample_idx, gq_dp_fail_word >>= 2) { + if (gq_dp_fail_word & 1) { + continue; + } + if (gt_value_ct) { + memcpy(>_raw, &(gt_main[sample_idx * gt_value_ct]), 2); + if (gt_value_ct > 1) { + is_haploid_or_0ploid = gt_raw >> 15; + } + } + const uint32_t sample_idx_lowbits = sample_idx % kBitsPerWordD2; + const uint32_t shifted_bit = 1U << sample_idx_lowbits; + DosageParseResult dpr = kDosageParseOk; + uintptr_t cur_geno = 3; + int32_t cur_dphase_delta = 0; + uint32_t hds_valid = 0; + uint32_t dosage_int; + if (!ParseBcfBiallelicHds(dosage_main, hds_main, dosage_value_ct, hds_value_ct, sample_idx, is_haploid_or_0ploid, dosage_is_gp, import_dosage_certainty, &dpr, &dosage_int, &cur_dphase_delta, &hds_valid)) { + if (hds_valid) { + const uint32_t dphase_halfdist1 = DphaseHalfdist(dosage_int + cur_dphase_delta); + const uint32_t dphase_halfdist2 = DphaseHalfdist(dosage_int - cur_dphase_delta); + if ((dphase_halfdist1 < dphase_erase_halfdist) || (dphase_halfdist2 < dphase_erase_halfdist)) { + // No need to fill cur_geno here, since it'll get corrected by + // --hard-call-threshold. + dosage_present_hw |= shifted_bit; + *dosage_main_iter++ = dosage_int; + if (cur_dphase_delta) { + dphase_present_hw |= shifted_bit; + *dphase_delta_iter++ = cur_dphase_delta; + } + } else { + // Not saving dosage, since it's too close to an integer + // (--dosage-erase-threshold). Just directly synthesize the + // hardcall we need. + cur_geno = (dosage_int + kDosage4th) / kDosageMid; + if (cur_geno == 1) { + // Since dphase_erase_halfdist >= 8193, dphase_halfdist1 and + // dphase_halfdist2 are both in [0, 8191] or [24577, 32768], so + // it's always appropraite to save hardcall-phase. + phasepresent_hw |= shifted_bit; + if (cur_dphase_delta > 0) { + phaseinfo_hw |= shifted_bit; + } + } + } + goto BcfConvertPhasedBiallelicDosage_geno_done; + } + // defer handling of unphased dosage + } else if (unlikely(!dpr)) { + return kBcfParseInvalidDosage; + } else if (dpr == kDosageParseForceMissing) { + goto BcfConvertPhasedBiallelicDosage_geno_done; + } + if (gt_main) { + if (is_haploid_or_0ploid) { + // gt_first == 0 -> missing (cur_geno == 3) + // gt_first == 2 -> hom-ref (cur_geno == 0) + // gt_first == 4 -> hom-alt (cur_geno == 2) + cur_geno = (gt_raw & 0xff) - 2; + // deliberate underflow + if (cur_geno > 3) { + cur_geno = 3; + } + } else if ((gt_value_ct == 2) || (gt_main[sample_idx * gt_value_ct + 2] == 0x81)) { + // diploid + const uint16_t bit_8 = gt_raw & 0x100; + unsigned char first_allele_idx_p1 = (gt_raw ^ bit_8) >> 1; + if (first_allele_idx_p1 > 3) { + first_allele_idx_p1 = 3; + } + unsigned char second_allele_idx_p1 = gt_raw >> 9; + if (second_allele_idx_p1 > 3) { + second_allele_idx_p1 = 3; + } + cur_geno = biallelic_gt_lookup[first_allele_idx_p1 + second_allele_idx_p1 * 4]; + half_call_error_bit2 |= cur_geno; + if ((cur_geno == 1) && bit_8) { + phasepresent_hw |= shifted_bit; + phaseinfo_hw |= (second_allele_idx_p1 & 1) << sample_idx_lowbits; + } + } + } + if (!dpr) { + // now actually handle the unphased dosage + const uint32_t cur_halfdist = BiallelicDosageHalfdist(dosage_int); + if (cur_halfdist < dosage_erase_halfdist) { + // ok for cur_geno to be 'wrong' for now, since it'll get corrected + // by --hard-call-threshold + dosage_present_hw |= shifted_bit; + *dosage_main_iter++ = dosage_int; + } else { + // Not saving dosage, since it's too close to an integer, except + // possibly in the implicit-phased-dosage edge case. + // If that integer actually conflicts with the hardcall, we must + // override the hardcall. + cur_geno = (dosage_int + kDosage4th) / kDosageMid; + if (phasepresent_hw & shifted_bit) { + if (cur_geno != 1) { + // Hardcall-phase no longer applies. + phasepresent_hw ^= shifted_bit; + } else if (cur_halfdist * 2 < dphase_erase_halfdist) { + // Implicit phased-dosage, e.g. 0|0.99. More stringent + // dosage_erase_halfdist applies. + dosage_present_hw |= shifted_bit; + *dosage_main_iter++ = dosage_int; + } + } + } + } + BcfConvertPhasedBiallelicDosage_geno_done: + genovec_word |= cur_geno << (2 * sample_idx_lowbits); + } + genovec[widx] = genovec_word | gq_dp_fail_word; + phasepresent_alias[widx] = phasepresent_hw; + phaseinfo_alias[widx] = phaseinfo_hw; + dosage_present_alias[widx] = dosage_present_hw; + dphase_present_alias[widx] = dphase_present_hw; + } + if (unlikely(half_call_error_bit2 & 4)) { + return kBcfParseHalfCallError; + } + *dosage_main_iter_ptr = dosage_main_iter; + *dphase_delta_iter_ptr = dphase_delta_iter; + return kBcfParseOk; +} + +// 1: int8 +// 2: int16 +// 3: int32 +// 5: float +// 7: char +static const unsigned char kBcfBytesPerElem[16] = {0, 1, 2, 4, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}; + +typedef struct BcfGenoToPgenCtxStruct { + BcfImportContext bic; + uint32_t hard_call_halfdist; + + unsigned char** thread_wkspaces; + + uint32_t* thread_bidxs[2]; + GparseRecord* gparse[2]; + const uintptr_t* block_allele_idx_offsets[2]; + + // PglErr set by main thread + BcfParseErr* bcf_parse_errs; + uintptr_t* err_vrec_idxs; + uint32_t parse_failed; +} BcfGenoToPgenCtx; + +THREAD_FUNC_DECL BcfGenoToPgenThread(void* raw_arg) { + ThreadGroupFuncArg* arg = S_CAST(ThreadGroupFuncArg*, raw_arg); + const uintptr_t tidx = arg->tidx; + BcfGenoToPgenCtx* ctx = S_CAST(BcfGenoToPgenCtx*, arg->sharedp->context); + + const BcfImportContext* bicp = &(ctx->bic); + const BcfImportBaseContext* bibcp = &(bicp->bibc); + const uint32_t sample_ct = bibcp->sample_ct; + const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); + const uint32_t sample_ctl = BitCtToWordCt(sample_ct); + const uint32_t hard_call_halfdist = ctx->hard_call_halfdist; + unsigned char* thread_wkspace = ctx->thread_wkspaces[tidx]; + uintptr_t* patch_01_set = nullptr; + AlleleCode* patch_01_vals = nullptr; + uintptr_t* patch_10_set = nullptr; + AlleleCode* patch_10_vals = nullptr; + uintptr_t* phasepresent = nullptr; + uintptr_t* phaseinfo = nullptr; + uintptr_t* dosage_present = nullptr; + Dosage* dosage_main = nullptr; + uintptr_t* dphase_present = nullptr; + SDosage* dphase_delta = nullptr; + SDosage* tmp_dphase_delta = R_CAST(SDosage*, thread_wkspace); + thread_wkspace = &(thread_wkspace[RoundUpPow2(sample_ct * sizeof(SDosage), kBytesPerVec)]); + uintptr_t* write_patch_01_set = nullptr; + AlleleCode* write_patch_01_vals = nullptr; + uintptr_t* write_patch_10_set = nullptr; + AlleleCode* write_patch_10_vals = nullptr; + uintptr_t* write_phasepresent = nullptr; + uintptr_t* write_phaseinfo = nullptr; + uintptr_t* write_dosage_present = nullptr; + Dosage* write_dosage_main = nullptr; + uintptr_t* write_dphase_present = nullptr; + SDosage* write_dphase_delta = nullptr; + uint32_t cur_allele_ct = 2; + uint32_t parity = 0; + BcfParseErr bcf_parse_err = kBcfParseOk; + uintptr_t vrec_idx = 0; + do { + const uintptr_t* block_allele_idx_offsets = ctx->block_allele_idx_offsets[parity]; + const uint32_t bidx_end = ctx->thread_bidxs[parity][tidx + 1]; + GparseRecord* cur_gparse = ctx->gparse[parity]; + + for (uint32_t bidx = ctx->thread_bidxs[parity][tidx]; bidx != bidx_end; ++bidx) { + GparseRecord* grp = &(cur_gparse[bidx]); + uint32_t patch_01_ct = 0; + uint32_t patch_10_ct = 0; + uint32_t cur_phasepresent_exists = 0; + uint32_t dosage_ct = 0; + uint32_t dphase_ct = 0; + unsigned char* record_start = grp->record_start; + GparseFlags gparse_flags = grp->flags; + if (gparse_flags == kfGparseNull) { + SetAllBits(2 * sample_ct, R_CAST(uintptr_t*, record_start)); + } else { + if (block_allele_idx_offsets) { + cur_allele_ct = block_allele_idx_offsets[bidx + 1] - block_allele_idx_offsets[bidx]; + } + uintptr_t* genovec = GparseGetPointers(thread_wkspace, sample_ct, cur_allele_ct, gparse_flags, &patch_01_set, &patch_01_vals, &patch_10_set, &patch_10_vals, &phasepresent, &phaseinfo, &dosage_present, &dosage_main, &dphase_present, &dphase_delta); + uintptr_t* write_genovec = GparseGetPointers(record_start, sample_ct, cur_allele_ct, gparse_flags, &write_patch_01_set, &write_patch_01_vals, &write_patch_10_set, &write_patch_10_vals, &write_phasepresent, &write_phaseinfo, &write_dosage_present, &write_dosage_main, &write_dphase_present, &write_dphase_delta); + const GparseReadBcfMetadata* metap = &(grp->metadata.read_bcf); + if ((metap->hds_vec_offset == UINT32_MAX) && (metap->dosage_vec_offset == UINT32_MAX)) { + if (!(gparse_flags & kfGparseHphase)) { + if (cur_allele_ct == 2) { + bcf_parse_err = BcfConvertUnphasedBiallelic(bibcp, metap, record_start, genovec); + } else { + bcf_parse_err = BcfConvertUnphasedMultiallelic(bibcp, metap, record_start, cur_allele_ct, &patch_01_ct, &patch_10_ct, genovec, patch_01_set, patch_01_vals, patch_10_set, patch_10_vals); + } + } else { + if (cur_allele_ct == 2) { + bcf_parse_err = BcfConvertPhasedBiallelic(bibcp, metap, record_start, genovec, phasepresent, phaseinfo); + } else { + bcf_parse_err = BcfConvertPhasedMultiallelic(bibcp, metap, record_start, cur_allele_ct, &patch_01_ct, &patch_10_ct, genovec, patch_01_set, patch_01_vals, patch_10_set, patch_10_vals, phasepresent, phaseinfo); + } + cur_phasepresent_exists = !AllWordsAreZero(phasepresent, sample_ctl); + } + if (unlikely(bcf_parse_err)) { + vrec_idx = metap->rec_idx; + goto BcfGenoToPgenThread_malformed; + } + } else { + Dosage* dosage_main_iter = dosage_main; + SDosage* dphase_delta_iter = dphase_delta; + if (cur_allele_ct == 2) { + bcf_parse_err = BcfConvertPhasedBiallelicDosage(bicp, metap, record_start, genovec, phasepresent, phaseinfo, dosage_present, dphase_present, &dosage_main_iter, &dphase_delta_iter); + } else { + // multiallelic dosage: shouldn't be possible to get here yet + exit(S_CAST(int32_t, kPglRetInternalError)); + } + if (unlikely(bcf_parse_err)) { + vrec_idx = metap->rec_idx; + goto BcfGenoToPgenThread_malformed; + } + dosage_ct = dosage_main_iter - dosage_main; + if (dosage_ct) { + dphase_ct = ApplyHardCallThreshPhased(dosage_present, dosage_main, dosage_ct, hard_call_halfdist, genovec, phasepresent, phaseinfo, dphase_present, dphase_delta, tmp_dphase_delta); + memcpy(write_dosage_present, dosage_present, sample_ctl * sizeof(intptr_t)); + memcpy(write_dosage_main, dosage_main, dosage_ct * sizeof(Dosage)); + if (dphase_ct) { + memcpy(write_dphase_present, dphase_present, sample_ctl * sizeof(intptr_t)); + memcpy(write_dphase_delta, dphase_delta, dphase_ct * sizeof(SDosage)); + } + } + cur_phasepresent_exists = !AllWordsAreZero(phasepresent, sample_ctl); + } + memcpy(write_genovec, genovec, sample_ctl2 * sizeof(intptr_t)); + if (patch_01_ct) { + memcpy(write_patch_01_set, patch_01_set, sample_ctl * sizeof(intptr_t)); + memcpy(write_patch_01_vals, patch_01_vals, patch_01_ct * sizeof(AlleleCode)); + } + if (patch_10_ct) { + memcpy(write_patch_10_set, patch_10_set, sample_ctl * sizeof(intptr_t)); + memcpy(write_patch_10_vals, patch_10_vals, patch_10_ct * sizeof(AlleleCode) * 2); + } + if (cur_phasepresent_exists || dphase_ct) { + memcpy(write_phasepresent, phasepresent, sample_ctl * sizeof(intptr_t)); + memcpy(write_phaseinfo, phaseinfo, sample_ctl * sizeof(intptr_t)); + } + } + + grp->metadata.write.patch_01_ct = patch_01_ct; + grp->metadata.write.patch_10_ct = patch_10_ct; + grp->metadata.write.phasepresent_exists = cur_phasepresent_exists; + grp->metadata.write.dosage_ct = dosage_ct; + grp->metadata.write.multiallelic_dosage_ct = 0; + grp->metadata.write.dphase_ct = dphase_ct; + grp->metadata.write.multiallelic_dphase_ct = 0; + } + while (0) { + BcfGenoToPgenThread_malformed: + ctx->bcf_parse_errs[tidx] = bcf_parse_err; + ctx->err_vrec_idxs[tidx] = vrec_idx; + ctx->parse_failed = 1; + break; + } + parity = 1 - parity; + } while (!THREAD_BLOCK_FINISH(arg)); + THREAD_RETURN; +} + +PglErr BcfToPgen(const char* bcfname, const char* preexisting_psamname, const char* const_fid, const char* dosage_import_field, MiscFlags misc_flags, ImportFlags import_flags, uint32_t no_samples_ok, uint32_t hard_call_thresh, uint32_t dosage_erase_thresh, double import_dosage_certainty, char id_delim, char idspace_to, int32_t vcf_min_gq, int32_t vcf_min_dp, int32_t vcf_max_dp, VcfHalfCall halfcall_mode, FamCol fam_cols, uint32_t max_thread_ct, char* outname, char* outname_end, ChrInfo* cip, uint32_t* pgen_generated_ptr, uint32_t* psam_generated_ptr) { + // Yes, lots of this is copied-and-pasted from VcfToPgen(), but there are + // enough differences that I don't think trying to handle them with the same + // function is wise. + + // Possible todo: make this take proper advantage of an index file when a + // chromosome filter has been specified. (This requires an upgrade to + // include/plink2_bgzf.) + unsigned char* bigstack_mark = g_bigstack_base; + unsigned char* bigstack_end_mark = g_bigstack_end; + FILE* bcffile = nullptr; + const char* bgzf_errmsg = nullptr; + FILE* pvarfile = nullptr; + uintptr_t vrec_idx = 0; + const uint32_t half_call_explicit_error = (halfcall_mode == kVcfHalfCallError); + PglErr reterr = kPglRetSuccess; + BcfParseErr bcf_parse_err = kBcfParseOk; + ThreadGroup tg; + PreinitThreads(&tg); + BcfGenoToPgenCtx ctx; + BgzfRawMtDecompressStream bgzf; + PreinitBgzfRawMtStream(&bgzf); + STPgenWriter spgw; + PreinitSpgw(&spgw); + { + // See TextFileOpenInternal(). + bcffile = fopen(bcfname, FOPEN_RB); + if (unlikely(!bcffile)) { + const uint32_t slen = strlen(bcfname); + if (!StrEndsWith(bcfname, ".bcf", slen)) { + logerrprintfww("Error: Failed to open %s : %s. (--bcf expects a complete filename; did you forget '.bcf' at the end?)\n", bcfname, strerror(errno)); + } else { + logerrprintfww(kErrprintfFopen, bcfname, strerror(errno)); + } + goto BcfToPgen_ret_OPEN_FAIL; + } + const uint32_t decompress_thread_ct = ClipU32(max_thread_ct - 1, 1, 4); + uint32_t header_size; + { + char bgzf_header[16]; + uint32_t nbytes = fread_unlocked(bgzf_header, 1, 16, bcffile); + if (unlikely(ferror(bcffile))) { + reterr = kPglRetReadFail; + goto BcfToPgen_ret_BGZF_FAIL; + } + if (unlikely((nbytes != 16) || (!IsBgzfHeader(bgzf_header)))) { + snprintf(g_logbuf, kLogbufSize, "Error: %s is not a BCF2 file.\n", bcfname); + goto BcfToPgen_ret_MALFORMED_INPUT_WW; + } + reterr = BgzfRawMtStreamInit(bgzf_header, decompress_thread_ct, bcffile, nullptr, &bgzf, &bgzf_errmsg); + if (unlikely(reterr)) { + goto BcfToPgen_ret_BGZF_FAIL; + } + unsigned char prefix_buf[9]; + unsigned char* prefix_end = &(prefix_buf[9]); + unsigned char* dummy = prefix_buf; + reterr = BgzfRawMtStreamRead(prefix_end, &bgzf, &dummy, &bgzf_errmsg); + if (unlikely(reterr)) { + goto BcfToPgen_ret_BGZF_FAIL; + } + if (unlikely((dummy != prefix_end) || (!memequal_k(prefix_buf, "BCF", 3)))) { + snprintf(g_logbuf, kLogbufSize, "Error: %s is not a BCF2 file.\n", bcfname); + goto BcfToPgen_ret_MALFORMED_INPUT_WW; + } + if (unlikely(prefix_buf[3] != 2)) { + if (prefix_buf[3] == 4) { + snprintf(g_logbuf, kLogbufSize, "Error: %s appears to be a BCF1 file; --bcf only supports BCF2. Use 'bcftools view' to convert it to a PLINK-readable BCF.\n", bcfname); + } else { + snprintf(g_logbuf, kLogbufSize, "Error: %s is not a BCF2 file.\n", bcfname); + } + goto BcfToPgen_ret_MALFORMED_INPUT_WW; + } + if (unlikely(prefix_buf[4] > 2)) { + snprintf(g_logbuf, kLogbufSize, "Error: %s appears to be formatted as BCFv2.%u; this " PROG_NAME_STR " build only supports v2.0-2.2. You may need to obtain an updated version of PLINK.\n", bcfname, prefix_buf[4]); + goto BcfToPgen_ret_MALFORMED_INPUT_WW; + } + memcpy(&header_size, &(prefix_buf[5]), sizeof(int32_t)); + if (unlikely(header_size < 59)) { + goto BcfToPgen_ret_MALFORMED_INPUT_GENERIC; + } + } + // can't be const due to how --vcf-idspace-to is implemented + // we could free this before the main conversion loop, but unlikely to be + // more than a few MB so I won't bother + // + 9 to simplify second pass + char* vcf_header; + { + char* vcf_header_alloc; + if (unlikely( + bigstack_alloc_c(header_size + (9 * k1LU), &vcf_header_alloc))) { + goto BcfToPgen_ret_NOMEM; + } + vcf_header = &(vcf_header_alloc[9]); + unsigned char* header_load_iter = R_CAST(unsigned char*, vcf_header); + unsigned char* header_load_end = &(header_load_iter[header_size]); + reterr = BgzfRawMtStreamRead(header_load_end, &bgzf, &header_load_iter, &bgzf_errmsg); + if (unlikely(reterr)) { + goto BcfToPgen_ret_BGZF_FAIL; + } + if (unlikely(header_load_iter != header_load_end)) { + goto BcfToPgen_ret_MALFORMED_INPUT_GENERIC; + } + } + if (unlikely(!memequal_k(&(vcf_header[header_size - 2]), "\n", 2))) { + goto BcfToPgen_ret_MALFORMED_TEXT_HEADER; + } + char* vcf_header_end = &(vcf_header[header_size - 1]); + const uint32_t allow_extra_chrs = (misc_flags / kfMiscAllowExtraChrs) & 1; + uint32_t dosage_import_field_slen = 0; + + uint32_t format_hds_search = 0; + + uint32_t unforced_gp = 0; + if (dosage_import_field) { + dosage_import_field_slen = strlen(dosage_import_field); + ctx.bic.dosage_is_gp = 0; + if (strequal_k(dosage_import_field, "HDS", dosage_import_field_slen)) { + format_hds_search = 1; + // special case: search for DS and HDS + dosage_import_field = &(dosage_import_field[1]); + dosage_import_field_slen = 2; + } else if (strequal_k(dosage_import_field, "GP-force", dosage_import_field_slen)) { + dosage_import_field = kGpText; + dosage_import_field_slen = 2; + ctx.bic.dosage_is_gp = 1; + } else if (strequal_k(dosage_import_field, "GP", dosage_import_field_slen)) { + unforced_gp = (import_dosage_certainty == 0.0); + ctx.bic.dosage_is_gp = 1; + } + } + + ctx.bic.bibc.qual_mins[0] = vcf_min_gq; + ctx.bic.bibc.qual_mins[1] = (vcf_min_dp == -1)? 0 : vcf_min_dp; + ctx.bic.bibc.qual_maxs[0] = 0x7fffffff; + ctx.bic.bibc.qual_maxs[1] = vcf_max_dp; + // sample_ct set later + if (halfcall_mode == kVcfHalfCallDefault) { + halfcall_mode = kVcfHalfCallError; + } + ctx.bic.bibc.halfcall_mode = halfcall_mode; + // Construct main biallelic GT-parsing lookup table. + // Low 2 bits of index = first_allele_idx_p1 (or 3 for END_OF_VECTOR), + // high 2 bits = second_allele_idx_p1. + // Table value is 2-bit genotype value, or 4 for half-call error. + { + unsigned char* biallelic_gt_lookup = ctx.bic.bibc.biallelic_gt_lookup; + biallelic_gt_lookup[0] = 3; + biallelic_gt_lookup[1] = 4; // half-call + biallelic_gt_lookup[2] = 4; + biallelic_gt_lookup[3] = 3; + + biallelic_gt_lookup[5] = 0; + biallelic_gt_lookup[6] = 1; + // ignore second value if first value is end-of-vector + biallelic_gt_lookup[7] = 3; + + biallelic_gt_lookup[10] = 2; + biallelic_gt_lookup[11] = 3; + + biallelic_gt_lookup[12] = 3; + biallelic_gt_lookup[13] = 0; + biallelic_gt_lookup[14] = 2; + biallelic_gt_lookup[15] = 3; + + if (halfcall_mode == kVcfHalfCallReference) { + biallelic_gt_lookup[1] = 0; + biallelic_gt_lookup[2] = 1; + } else if (halfcall_mode == kVcfHalfCallHaploid) { + biallelic_gt_lookup[1] = 0; + biallelic_gt_lookup[2] = 2; + } else if (halfcall_mode == kVcfHalfCallMissing) { + biallelic_gt_lookup[1] = 3; + biallelic_gt_lookup[2] = 3; + } + + biallelic_gt_lookup[4] = biallelic_gt_lookup[1]; + biallelic_gt_lookup[8] = biallelic_gt_lookup[2]; + biallelic_gt_lookup[9] = biallelic_gt_lookup[6]; +#ifdef USE_AVX2 + memcpy(&(biallelic_gt_lookup[16]), biallelic_gt_lookup, 16); +#endif + } + + // always positive + ctx.bic.dosage_erase_halfdist = kDosage4th - dosage_erase_thresh; + + ctx.bic.import_dosage_certainty = import_dosage_certainty; + + // Scan the text header block. + // We perform the same validation as --vcf. In addition, we need to + // construct the contig and generic-string dictionaries; the latter may + // require awareness of IDX keys (TODO: fix this in plink 1.9). + // (There is also a comment in the specification about explicitly + // specifying a dictionary with a ##dictionary line, but AFAICT that is + // left over from early brainstorming, is not consistent with the rest of + // the design, and is not supported by bcftools so I assume it's a spec + // error.) + // hrec_add_idx() in htslib's vcf.c always puts IDX at the end of the + // header line, so I'll be lazy and print a not-yet-supported error message + // if it's positioned elsewhere for now. + uint32_t chrset_present = 0; + uint32_t contig_string_idx_end = 0; + uint32_t fif_string_idx_end = 1; // fif = filter, info, format; initialized with FILTER:PASS + uint32_t explicit_idx_keys = 2; // 2 = unknown status + uint32_t header_line_idx = 1; // uint32 ok since uncompressed size < 2^32 + char* line_iter = vcf_header; + for (; ; ++header_line_idx) { + if (unlikely(line_iter == vcf_header_end)) { + logerrputs("Error: No #CHROM header line in BCF text header block.\n"); + goto BcfToPgen_ret_MALFORMED_INPUT; + } + if (unlikely(*line_iter != '#')) { + snprintf(g_logbuf, kLogbufSize, "Error: Line %u in BCF text header block does not start with '#'.\n", header_line_idx); + goto BcfToPgen_ret_MALFORMED_INPUT_WW; + } + if (line_iter[1] != '#') { + break; + } + char* line_main = &(line_iter[2]); + char* line_end = AdvPastDelim(line_main, '\n'); + line_iter = line_end; + // Recognized header lines: + // ##fileformat: discard (regenerate; todo: conditionally error out) + // ##fileDate: discard (regenerate) + // ##source: discard (regenerate) + // ##contig: ID added to contig dictionary, conditionally keep + // ##FILTER: ID added to dictionary, otherwise passed through unchanged + // ##INFO: note presence of INFO:PR, note presence of at least one non-PR + // field, keep data (though, if INFO:PR is the *only* field, + // omit it from the .pvar for consistency with --make-pgen + // default); ID added to dictionary + // update (8 Sep 2017): nonflag INFO:PR is noted, and not treated + // specially unless provisional-reference INFO:PR output would + // conflict with it + // ##FORMAT: note presence of FORMAT:GT and FORMAT:GP, discard + // (regenerate); ID added to dictionary + // ##chrSet: if recognized, perform consistency check and/or update + // chr_info + // + // Everything else (##reference, etc.) is passed through + // unchanged. + // + // Because of how ##contig is handled (we only keep the lines which + // correspond to chromosomes/contigs actually present in the BCF, and not + // filtered out), we wait until the second pass to write the .pvar. + if (StrStartsWithUnsafe(line_main, "chrSet=<")) { + if (unlikely(chrset_present)) { + logerrputs("Error: Multiple ##chrSet header lines in BCF text header block.\n"); + goto BcfToPgen_ret_MALFORMED_INPUT; + } + chrset_present = 1; + // .pvar loader will print a warning if necessary + reterr = ReadChrsetHeaderLine(&(line_main[8]), "--bcf file", misc_flags, header_line_idx, cip); + if (unlikely(reterr)) { + goto BcfToPgen_ret_1; + } + continue; + } + // don't check for subsequent '=' just in case IDX= appears first + const uint32_t is_contig_line = StrStartsWithUnsafe(line_main, "contig= is a digit. + // - Last nondigit characters inside <> are ",IDX=". + uint32_t cur_explicit_idx_key = 0; + if (IsDigit(*line_last_iter)) { + do { + --line_last_iter; + } while (IsDigit(*line_last_iter)); + cur_explicit_idx_key = StrStartsWithUnsafe(&(line_last_iter[-4]), ",IDX="); + } + const char* id_start = &(line_main[11 - 2 * is_info_line]); + if (explicit_idx_keys == 2) { + explicit_idx_keys = cur_explicit_idx_key; + if (!cur_explicit_idx_key) { + // The BCF specification does not require IDX= to be at the end of + // the line, but it does require it to be present on all + // contig/FILTER/INFO/FORMAT lines if it's present on any. + // Thus, if a conforming writer puts IDX= in the middle of any lines, + // we'll detect that either here, or in the next cur_explicit_idx_key + // != explicit_idx_keys check. + reterr = BcfHeaderLineIdxCheck(&(id_start[-4]), header_line_idx); + if (unlikely(reterr)) { + goto BcfToPgen_ret_1; + } + } + } else { + if (unlikely(cur_explicit_idx_key != explicit_idx_keys)) { + reterr = BcfHeaderLineIdxCheck(&(id_start[-4]), header_line_idx); + if (reterr != kPglRetSuccess) { + goto BcfToPgen_ret_1; + } + logerrputs("Error: Some contig/FILTER/INFO/FORMAT line(s) in the BCF text header block have\nIDX keys, and others don't; this is not permitted by the specification.\n"); + goto BcfToPgen_ret_MALFORMED_INPUT; + } + } + // Special case: FILTER:PASS IDX=0 line is implicit, we must be + // indifferent to whether it's actually present. (Though when it is + // present, it's subject to the same IDX= always/never-present rule as + // the other header lines, which is why we don't perform this check + // earlier.) + if (is_filter_line && memequal_k(id_start, "PASS,", 5)) { + continue; + } + if (!cur_explicit_idx_key) { + if (is_contig_line) { + ++contig_string_idx_end; + } else { + ++fif_string_idx_end; + } + } else { + uint32_t val; + if (unlikely(ScanUintDefcap(&(line_last_iter[1]), &val))) { + snprintf(g_logbuf, kLogbufSize, "Error: Invalid IDX= value on line %u of BCF text header block.\n", header_line_idx); + goto BcfToPgen_ret_MALFORMED_INPUT_2; + } + if (is_contig_line) { + if (val >= contig_string_idx_end) { + contig_string_idx_end = val + 1; + } + } else { + // Easier to check for duplicates on the second pass. + if (val >= fif_string_idx_end) { + fif_string_idx_end = val + 1; + } + } + } + if (id_start[-1] != '=') { + snprintf(g_logbuf, kLogbufSize, "Error: Line %u in BCF text header block is malformed.\n", header_line_idx); + goto BcfToPgen_ret_MALFORMED_INPUT_2; + } + const char* id_end = strchrnul_n(id_start, ','); + if (*id_end == '\n') { + snprintf(g_logbuf, kLogbufSize, "Error: Line %u in BCF text header block is malformed.\n", header_line_idx); + goto BcfToPgen_ret_MALFORMED_INPUT_2; + } + } + + FinalizeChrset(misc_flags, cip); + // don't call FinalizeChrInfo here, since this may be followed by --pmerge, + // etc. + + if (unlikely(!StrStartsWithUnsafe(line_iter, "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO"))) { + snprintf(g_logbuf, kLogbufSize, "Error: Header line %u of BCF text header block does not have expected field sequence after #CHROM.\n", header_line_idx); + goto BcfToPgen_ret_MALFORMED_INPUT_WW; + } + uint32_t sample_ct = 0; + { + char* linebuf_iter = &(line_iter[strlen("#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO")]); + if (StrStartsWithUnsafe(linebuf_iter, "\tFORMAT\t")) { + reterr = VcfSampleLine(preexisting_psamname, const_fid, misc_flags, import_flags, fam_cols, id_delim, idspace_to, 'b', &(linebuf_iter[strlen("\tFORMAT\t")]), outname, outname_end, &sample_ct); + if (unlikely(reterr)) { + goto BcfToPgen_ret_1; + } + } + } + if (unlikely((!sample_ct) && (!no_samples_ok))) { + logerrputs("Error: No samples in BCF text header block. (This is only permitted when you\nhaven't specified another operation which requires genotype or sample\ninformation.)\n"); + goto BcfToPgen_ret_INCONSISTENT_INPUT; + } + if (unlikely(sample_ct >= (1 << 24))) { + snprintf(g_logbuf, kLogbufSize, "Error: BCF text header block has %u sample IDs, which is larger than the BCF limit of 2^24 - 1.\n", sample_ct); + goto BcfToPgen_ret_MALFORMED_INPUT_WW; + } + ctx.bic.bibc.sample_ct = sample_ct; + const uint32_t sample_ctl2 = NypCtToWordCt(sample_ct); + + // Rescan header to save FILTER/INFO/FORMAT and contig ID dictionaries. + // (We could also print a warning when an INFO key type declaration is + // inconsistent with any variant entry, but that's not essential for + // decoding; let's get this working first.) + uintptr_t* bcf_contig_keep; + const char** contig_names; + uint32_t* contig_slens; + const char** fif_strings; + uint32_t* fif_slens; + uintptr_t* info_flags; + uintptr_t* bcf_contig_seen; + uintptr_t* sample_nypbuf; + if (unlikely( + bigstack_calloc_w(BitCtToWordCt(contig_string_idx_end), &bcf_contig_keep) || + bigstack_calloc_kcp(contig_string_idx_end, &contig_names) || + bigstack_calloc_u32(contig_string_idx_end, &contig_slens) || + bigstack_calloc_kcp(fif_string_idx_end, &fif_strings) || + bigstack_calloc_u32(fif_string_idx_end, &fif_slens) || + bigstack_calloc_w(BitCtToWordCt(fif_string_idx_end), &info_flags) || + bigstack_calloc_w(BitCtToWordCt(contig_string_idx_end), &bcf_contig_seen) || + bigstack_alloc_w(sample_ctl2, &sample_nypbuf))) { + goto BcfToPgen_ret_NOMEM; + } + const uint32_t header_line_ct = header_line_idx; + // sidx = string index + uint32_t gt_sidx = 0; // replaces format_gt_present + uint32_t gq_sidx = 0; // replaces format_gq_relevant + uint32_t dp_sidx = 0; // replaces format_dp_relevant + uint32_t dosage_sidx = 0; // replaces format_dosage_relevant + uint32_t hds_sidx = 0; // replaces later uses of format_hds_search + + // replaces info_pr_present... except that UINT32_MAX is the not-present + // value, just in case there's an INFO:PASS field for some reason. + uint32_t pr_sidx = UINT32_MAX; + + uint32_t info_pr_nonflag_present = 0; + uint32_t info_nonpr_present = 0; + + // we don't want to clutter the second pass with too many instances of + // fwrite_ck(), so we compute FILTER-column, INFO-column and allele length + // bounds, and then size the write buffer accordingly. + uint32_t other_slen_ubound = kMaxFloatGSlen; // QUAL, alleles, etc. + uint64_t filter_info_slen_ubound = 1; + { + unsigned char* tmp_alloc_end = bigstack_end_mark; + if (StoreStringAtEndK(g_bigstack_base, "PASS", strlen("PASS"), &tmp_alloc_end, &(fif_strings[0]))) { + goto BcfToPgen_ret_NOMEM; + } + fif_slens[0] = 4; + line_iter = vcf_header; + uint32_t contig_idx = 0; + uint32_t fif_idx = 1; + uint32_t cur_header_idx = 0; + for (header_line_idx = 1; header_line_idx != header_line_ct; ++header_line_idx) { + char* line_main = &(line_iter[2]); + char* line_end = AdvPastDelim(line_main, '\n'); + line_iter = line_end; + const uint32_t is_contig_line = StrStartsWithUnsafe(line_main, "contig= other_slen_ubound) { + other_slen_ubound = id_slen; + } + } + if (is_contig_line || is_filter_line) { + continue; + } + if (is_info_line) { + const uintptr_t is_flag = StrStartsWithUnsafe(id_end, ",Number=0,Type=Flag,Description="); + info_flags[cur_header_idx / kBitsPerWord] |= is_flag << (cur_header_idx % kBitsPerWord); + if (strequal_k(id_start, "PR", id_slen)) { + if (unlikely((pr_sidx != UINT32_MAX) || info_pr_nonflag_present)) { + logerrputs("Error: Duplicate INFO:PR line in BCF text header block.\n"); + goto BcfToPgen_ret_MALFORMED_INPUT; + } + if (is_flag) { + pr_sidx = cur_header_idx; + } else { + info_pr_nonflag_present = 1; + logerrprintfww("Warning: Line %u of BCF text header block has an unexpected definition of INFO:PR. This interferes with a few merge and liftover operations.\n", header_line_idx); + } + } else { + info_nonpr_present = 1; + } + continue; + } + // FORMAT + if (StrStartsWithUnsafe(id_start, "GT,Number=")) { + if (unlikely(gt_sidx)) { + logerrputs("Error: Duplicate FORMAT:GT line in BCF text header block.\n"); + goto BcfToPgen_ret_MALFORMED_INPUT; + } + if (unlikely(!StrStartsWithUnsafe(&(id_start[strlen("GT,Number=")]), "1,Type=String,Description="))) { + snprintf(g_logbuf, kLogbufSize, "Error: Line %u of BCF text header block does not have expected FORMAT:GT format.\n", header_line_idx); + goto BcfToPgen_ret_MALFORMED_INPUT_WW; + } + gt_sidx = cur_header_idx; + } else if ((vcf_min_gq != -1) && StrStartsWithUnsafe(id_start, "GQ,Number=1,Type=")) { + if (unlikely(gq_sidx)) { + logerrputs("Error: Duplicate FORMAT:GQ header line in BCF text header block.\n"); + goto BcfToPgen_ret_MALFORMED_INPUT; + } + gq_sidx = cur_header_idx; + } else if (((vcf_min_dp != -1) || (vcf_max_dp != 0x7fffffff)) && StrStartsWithUnsafe(id_start, "DP,Number=1,Type=")) { + if (unlikely(dp_sidx)) { + logerrputs("Error: Duplicate FORMAT:DP header line in BCF text header block.\n"); + goto BcfToPgen_ret_MALFORMED_INPUT; + } + dp_sidx = cur_header_idx; + } else if (dosage_import_field) { + if ((id_slen == dosage_import_field_slen) && memequal(id_start, dosage_import_field, dosage_import_field_slen)) { + if (unlikely(dosage_sidx)) { + snprintf(g_logbuf, kLogbufSize, "Error: Duplicate FORMAT:%s header line in BCF text header block.\n", dosage_import_field); + goto BcfToPgen_ret_MALFORMED_INPUT_WW; + } + dosage_sidx = cur_header_idx; + } else if (format_hds_search && StrStartsWithUnsafe(id_start, "HDS,")) { + if (unlikely(hds_sidx)) { + logerrputs("Error: Duplicate FORMAT:HDS header line in BCF text header block.\n"); + goto BcfToPgen_ret_MALFORMED_INPUT; + } + hds_sidx = cur_header_idx; + } else if (unforced_gp && StrStartsWithUnsafe(id_start, "DS,")) { + logerrputs("Error: --bcf dosage=GP specified, but --import-dosage-certainty was not and\nFORMAT:DS header line is present.\nSince " PROG_NAME_STR " collapses genotype probabilities down to dosages (even when\nperforming simple operations like \"" PROG_NAME_STR " --bcf ... --export bgen-1.2 ...\"),\n'dosage=GP' almost never makes sense in this situation. Either change it to\n'dosage=DS' (if dosages are good enough for your analysis), or use another\nprogram to work with the genotype probabilities.\nThere is one notable exception to the preceding recommendation: you are writing\na script to process BCF files that are guaranteed to have FORMAT:GP, but may or\nmay not have FORMAT:DS. You can use 'dosage=GP-force' to suppress this error\nin that situation.\n"); + goto BcfToPgen_ret_INCONSISTENT_INPUT; + } + } + } + BigstackEndSet(tmp_alloc_end); + } + const uint32_t require_gt = (import_flags / kfImportVcfRequireGt) & 1; + if (unlikely((!gt_sidx) && require_gt)) { + logerrputs("Error: No FORMAT:GT field in BCF text header block, when --vcf-require-gt was\nspecified.\n"); + goto BcfToPgen_ret_INCONSISTENT_INPUT; + } + if ((!gq_sidx) && (vcf_min_gq != -1)) { + logerrputs("Warning: No FORMAT:GQ field in BCF text header block. --vcf-min-gq ignored.\n"); + } + if ((!dp_sidx) && ((vcf_max_dp != 0x7fffffff) || (vcf_min_dp != -1))) { + logerrputs("Warning: No FORMAT:DP field in BCF text header block. --vcf-{max,min}-dp\nignored.\n"); + } + if (format_hds_search) { + if (!hds_sidx) { + if (!dosage_sidx) { + logerrputs("Warning: No FORMAT:DS or :HDS field in BCF text header block. Dosages will not\nbe imported.\n"); + } else { + logerrputs("Warning: No FORMAT:HDS field in BCF text header block. Dosages will be\nimported (from FORMAT:DS), but phase information will be limited or absent.\n"); + } + } + } else if ((!dosage_sidx) && dosage_import_field) { + logerrprintfww("Warning: No FORMAT:%s field in BCF text header block. Dosages will not be imported.\n", dosage_import_field); + } + + unsigned char* bigstack_end_mark2 = g_bigstack_end; + uintptr_t loadbuf_size = RoundDownPow2(bigstack_left() / 2, kEndAllocAlign); +#ifdef __LP64__ + // l_shared and l_indiv are of type uint32_t, so we never need to look at + // more than 8 GiB at a time. + if (loadbuf_size > (k1LU << 33)) { + loadbuf_size = k1LU << 33; + } +#endif + // guaranteeing at least this many bytes allocated at bigstack_end + // simplifies a bit of parsing logic + if (loadbuf_size < (1 << 18)) { + goto BcfToPgen_ret_NOMEM; + } + // Placed at end of arena so we can shrink it before the second pass + // without fragmenting memory. + unsigned char* loadbuf = S_CAST(unsigned char*, bigstack_end_alloc_raw(loadbuf_size)); + + uint32_t variant_ct = 0; + uintptr_t max_variant_ct = bigstack_left() / sizeof(intptr_t); + if (pr_sidx != UINT32_MAX) { + // nonref_flags + max_variant_ct -= BitCtToAlignedWordCt(max_variant_ct) * kWordsPerVec; + } +#ifdef __LP64__ + if (max_variant_ct > 0x7ffffffd) { + max_variant_ct = 0x7ffffffd; + } +#endif + // max(32, l_shared + l_indiv - 24) + // (there are cheaper ways to handle gt_present, but the memory savings are + // unlikely to be significant.) + uintptr_t loadbuf_size_needed = 32; + + // Sum of GT, DS/GP, HDS, GQ, and DP vector lengths. + uint32_t max_observed_rec_vecs = 0; + + const uint32_t max_variant_ctaw = BitCtToAlignedWordCt(max_variant_ct); + // don't need dosage_flags or dphase_flags; dosage overrides GT so slow + // parse needed + uintptr_t* nonref_flags = nullptr; + if (pr_sidx != UINT32_MAX) { + nonref_flags = S_CAST(uintptr_t*, bigstack_alloc_raw_rd(max_variant_ctaw * sizeof(intptr_t))); + } + uintptr_t* nonref_flags_iter = nonref_flags; + uintptr_t* allele_idx_offsets = R_CAST(uintptr_t*, g_bigstack_base); + uintptr_t nonref_word = 0; + uintptr_t allele_idx_end = 0; + uint32_t max_allele_ct = 0; + uint32_t phase_or_dosage_found = 0; + while (1) { + ++vrec_idx; // 1-based since it's only used in error messages + unsigned char* loadbuf_read_iter = loadbuf; + reterr = BgzfRawMtStreamRead(&(loadbuf[32]), &bgzf, &loadbuf_read_iter, &bgzf_errmsg); + if (unlikely(reterr)) { + goto BcfToPgen_ret_BGZF_FAIL_N; + } + if (&(loadbuf[32]) != loadbuf_read_iter) { + if (likely(loadbuf_read_iter == loadbuf)) { + // EOF + // possible todo: verify empty block is present at end + break; + } + goto BcfToPgen_ret_VREC_GENERIC; + } + const uint32_t* vrec_header = R_CAST(uint32_t*, loadbuf); + // IMPORTANT: Official specification is wrong about the ordering of these + // fields as of Feb 2020!! The correct ordering can be inferred from + // bcf_read1_core() in htslib vcf.c: + // [0]: l_shared + // [1]: l_indiv + // [2]: chrom + // [3]: pos + // [4]: rlen + // [5]: qual, NOT n_allele_info + // [6]: low 16 bits n_info, high 16 bits n_allele + // [7]: low 24 bits n_sample, high 8 bits n_fmt + const uint32_t l_shared = vrec_header[0]; + const uint32_t l_indiv = vrec_header[1]; + const uint32_t chrom = vrec_header[2]; + // Can ignore pos and qual on this pass. (QUAL/FILTER enforcement is now + // handled by the .pvar loader.) Always ignore rlen for now, though we + // may want to add a consistency check later. + const uint32_t n_allele = vrec_header[6] >> 16; + const uint32_t n_info = vrec_header[6] & 0xffff; + const uint32_t n_sample = vrec_header[7] & 0xffffff; + const uint32_t n_fmt = vrec_header[7] >> 24; + + if (unlikely((l_shared < 24) || (chrom >= contig_string_idx_end) || (n_sample != sample_ct))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + const uint32_t contig_slen = contig_slens[chrom]; + const uint64_t second_load_size = l_shared + S_CAST(uint64_t, l_indiv) - 24; + if (unlikely((!contig_slen) || (second_load_size > loadbuf_size))) { + goto BcfToPgen_ret_NOMEM; + } + if (second_load_size > loadbuf_size_needed) { + loadbuf_size_needed = second_load_size; + } + loadbuf_read_iter = loadbuf; + unsigned char* indiv_end = &(loadbuf[second_load_size]); + reterr = BgzfRawMtStreamRead(indiv_end, &bgzf, &loadbuf_read_iter, &bgzf_errmsg); + if (unlikely(reterr)) { + goto BcfToPgen_ret_BGZF_FAIL_N; + } + if (unlikely(indiv_end != loadbuf_read_iter)) { + goto BcfToPgen_ret_VREC_GENERIC; + } + + // We've now decompressed to the end of the variant record, and can + // safely skip the variant with "continue;". There are currently two + // cases where we might want to do this: chromosome filters, and + // --vcf-require-gt. + if (IsSet(bcf_contig_seen, chrom)) { + if (!IsSet(bcf_contig_keep, chrom)) { + continue; + } + } else if (!require_gt) { + // Don't want to mutate cip in require_gt case until we know the contig + // is being kept. + uint32_t cur_chr_code; + reterr = GetOrAddChrCode(contig_names[chrom], "--bcf file", 0, contig_slen, allow_extra_chrs, cip, &cur_chr_code); + if (unlikely(reterr)) { + goto BcfToPgen_ret_1; + } + SetBit(chrom, bcf_contig_seen); + if (!IsSet(cip->chr_mask, cur_chr_code)) { + continue; + } + SetBit(chrom, bcf_contig_keep); + } + const unsigned char* shared_end = indiv_end - l_indiv; + const unsigned char* parse_iter = shared_end; + + const unsigned char* gt_start = nullptr; + const unsigned char* qual_starts[2]; + qual_starts[0] = nullptr; + qual_starts[1] = nullptr; + const unsigned char* dosage_start = nullptr; + const unsigned char* hds_start = nullptr; + uint32_t cur_observed_rec_vecs = 0; + for (uint32_t fmt_idx = 0; fmt_idx != n_fmt; ++fmt_idx) { + // 1. typed int indicating which FORMAT field + // 2. shared type descriptor for each entry (usually a single byte) + // 3. sample_ct entries + uint32_t sidx; + if (unlikely(ScanBcfTypedInt(&parse_iter, &sidx) || (parse_iter > indiv_end) || (sidx >= fif_string_idx_end))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + const uint32_t key_slen = fif_slens[sidx]; + const unsigned char* type_start = parse_iter; + uint32_t value_type; + uint32_t value_ct; + if (unlikely((!key_slen) || ScanBcfType(&parse_iter, &value_type, &value_ct) || (parse_iter > indiv_end))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + const unsigned char* vec_start = parse_iter; + if (value_ct) { + const uint32_t bytes_per_elem = kBcfBytesPerElem[value_type]; + const uint64_t vec_byte_ct = bytes_per_elem * S_CAST(uint64_t, value_ct) * sample_ct; + if (unlikely((!bytes_per_elem) || (S_CAST(uint64_t, indiv_end - parse_iter) < vec_byte_ct))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + parse_iter = &(parse_iter[vec_byte_ct]); + } + // defend against FORMAT:PASS edge case + if (!sidx) { + continue; + } + if (sidx == gt_sidx) { + gt_start = type_start; + } else if (sidx == gq_sidx) { + qual_starts[0] = type_start; + } else if (sidx == dp_sidx) { + qual_starts[1] = type_start; + } else if (sidx == dosage_sidx) { + dosage_start = type_start; + } else if (sidx == hds_sidx) { + hds_start = type_start; + } else { + continue; + } +#ifdef __LP64__ + // bcf-type guaranteed to fit in <= 16 bytes + ++cur_observed_rec_vecs; +#else + cur_observed_rec_vecs += DivUp(S_CAST(uintptr_t, vec_start - type_start), kBytesPerVec); +#endif + cur_observed_rec_vecs += DivUp(S_CAST(uintptr_t, parse_iter - vec_start), kBytesPerVec); + } + if (require_gt) { + if (!gt_start) { + continue; + } + if (!IsSet(bcf_contig_seen, chrom)) { + uint32_t cur_chr_code; + reterr = GetOrAddChrCode(contig_names[chrom], "--bcf file", 0, strlen(contig_names[chrom]), allow_extra_chrs, cip, &cur_chr_code); + if (unlikely(reterr)) { + goto BcfToPgen_ret_1; + } + SetBit(chrom, bcf_contig_seen); + if (!IsSet(cip->chr_mask, cur_chr_code)) { + continue; + } + SetBit(chrom, bcf_contig_keep); + } + } + // We finally know for sure that we need to convert this variant. + + // Remainder of l_shared: + // ID (typed string) + // REF+ALT (n_allele typed strings) + // FILTER (typed vector of integers) + // INFO (n_info (typed integer, typed vector) pairs) + // We scan these fields to compute an upper bound on the necessary .pvar + // write-buffer size. In the unlikely pr_sidx != UINT32_MAX case, we + // also need to scan for presence of INFO:PR. + parse_iter = loadbuf; + // ID, REF, ALT + const uint32_t str_ignore_ct = n_allele + 1; + for (uint32_t uii = 0; uii != str_ignore_ct; ++uii) { + const char* str_start; + uint32_t slen; + if (unlikely(ScanBcfTypedString(shared_end, &parse_iter, &str_start, &slen))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + if (slen > other_slen_ubound) { + other_slen_ubound = slen; + } + } + // FILTER + uint32_t value_type; + uint32_t value_ct; + if (unlikely(ScanBcfType(&parse_iter, &value_type, &value_ct) || (parse_iter > shared_end))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + if (value_ct) { + const uint32_t value_type_m1 = value_type - 1; + const uintptr_t vec_byte_ct = S_CAST(uintptr_t, value_ct) << value_type_m1; + if (unlikely((value_type_m1 > 2) || (S_CAST(uintptr_t, shared_end - parse_iter) < vec_byte_ct))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + uint64_t cur_filter_slen = value_ct - 1; // delimiters + if (!value_type_m1) { + for (uint32_t filter_idx = 0; filter_idx != value_ct; ++filter_idx) { + const uint32_t cur_val = parse_iter[filter_idx]; + if (cur_val == 0x80) { + // missing + ++cur_filter_slen; + } else { + const uint32_t key_slen = fif_slens[cur_val]; + if (unlikely((cur_val >= fif_string_idx_end) || (cur_val > 0x80) || (!key_slen))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + cur_filter_slen += key_slen; + } + } + } else if (value_type_m1 == 1) { +#ifdef __arm__ +# error "Unaligned accesses in BcfToPgen()." +#endif + const uint16_t* filter_vec_alias = R_CAST(const uint16_t*, parse_iter); + for (uint32_t filter_idx = 0; filter_idx != value_ct; ++filter_idx) { + const uint32_t cur_val = filter_vec_alias[filter_idx]; + if (cur_val == 0x8000) { + // missing + ++cur_filter_slen; + } else { + // loadbuf_size >= 2^18, so fif_slens[65535] won't segfault + const uint32_t key_slen = fif_slens[cur_val]; + if (unlikely((cur_val >= fif_string_idx_end) || (cur_val > 0x8000) || (!key_slen))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + cur_filter_slen += key_slen; + } + } + } else { + const uint32_t* filter_vec_alias = R_CAST(const uint32_t*, parse_iter); + for (uint32_t filter_idx = 0; filter_idx != value_ct; ++filter_idx) { + const uint32_t cur_val = filter_vec_alias[filter_idx]; + if (cur_val == 0x80000000U) { + // missing + ++cur_filter_slen; + } else { + if (unlikely(cur_val >= fif_string_idx_end)) { + goto BcfToPgen_ret_VREC_GENERIC; + } + const uint32_t key_slen = fif_slens[cur_val]; + if (unlikely(!key_slen)) { + goto BcfToPgen_ret_VREC_GENERIC; + } + cur_filter_slen += key_slen; + } + } + } + if (cur_filter_slen > filter_info_slen_ubound) { + filter_info_slen_ubound = cur_filter_slen; + } + parse_iter = &(parse_iter[vec_byte_ct]); + } + // INFO + uintptr_t info_pr_here = 0; + uint64_t cur_info_slen_ubound = n_info - 1; + for (uint32_t uii = 0; uii != n_info; ++uii) { + uint32_t sidx; + if (unlikely(ScanBcfTypedInt(&parse_iter, &sidx) || (parse_iter > shared_end) || (sidx >= fif_string_idx_end))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + const uint32_t key_slen = fif_slens[sidx]; + if (unlikely((!key_slen) || ScanBcfType(&parse_iter, &value_type, &value_ct) || (parse_iter > shared_end))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + cur_info_slen_ubound += key_slen; + if (value_ct) { + const uint32_t bytes_per_elem = kBcfBytesPerElem[value_type]; + const uint64_t vec_byte_ct = bytes_per_elem * S_CAST(uint64_t, value_ct); + if (unlikely((!bytes_per_elem) || (S_CAST(uint64_t, shared_end - parse_iter) < vec_byte_ct))) { + goto BcfToPgen_ret_VREC_GENERIC; + } + if (value_type == 1) { + // int8, max len 4 ("-127"), add 1 for comma + cur_info_slen_ubound += 5 * S_CAST(uint64_t, value_ct); + } else if (value_type == 2) { + // int16, max len 6 + cur_info_slen_ubound += 7 * S_CAST(uint64_t, value_ct); + } else if (value_type == 3) { + // int32, max len 11 + cur_info_slen_ubound += 12 * S_CAST(uint64_t, value_ct); + } else if (value_type == 5) { + cur_info_slen_ubound += (kMaxFloatGSlen + 1) * S_CAST(uint64_t, value_ct); + } else { + // string + cur_info_slen_ubound += value_ct + 1; + } + parse_iter = &(parse_iter[vec_byte_ct]); + } else { + // tolerate value_type == value_ct == 0 "untyped-missing" special + // case, but only if field is of type Flag. Note that this means the + // flag IS present (and yes, bcftools emits this since it's compact). + // + // value_ct == 0, value_type == 7 is also valid (string, missing). + // + // Otherwise, value_ct == 0 should not happen. + if (!IsSet(info_flags, sidx)) { + if (unlikely(value_ct != 7)) { + goto BcfToPgen_ret_VREC_GENERIC; + } + cur_info_slen_ubound += 2; + } + } + if (sidx == pr_sidx) { + if (unlikely(info_pr_here)) { + putc_unlocked('\n', stdout); + snprintf(g_logbuf, kLogbufSize, "Error: Variant record #%" PRIuPTR " in --bcf file has multiple INFO:PR entries.\n", vrec_idx); + goto BcfToPgen_ret_MALFORMED_INPUT_WW; + } + info_pr_here = 1; + } + } + if (cur_info_slen_ubound > filter_info_slen_ubound) { + filter_info_slen_ubound = cur_info_slen_ubound; + } + if (unlikely(parse_iter != shared_end)) { + goto BcfToPgen_ret_VREC_GENERIC; + } + + if (max_allele_ct < n_allele) { + if (n_allele > (kPglMaxAltAlleleCt + 1)) { + putc_unlocked('\n', stdout); + logerrprintfww("Error: Variant record #%" PRIuPTR " in --bcf file has %u alleles; this build of " PROG_NAME_STR " is limited to %u.\n", vrec_idx, n_allele, kPglMaxAltAlleleCt + 1); + reterr = kPglRetNotYetSupported; + goto BcfToPgen_ret_1; + } + max_allele_ct = n_allele; + } + if (max_observed_rec_vecs < cur_observed_rec_vecs) { + max_observed_rec_vecs = cur_observed_rec_vecs; + } + allele_idx_offsets[variant_ct] = allele_idx_end; + allele_idx_end += n_allele; + const uint32_t variant_idx_lowbits = variant_ct % kBitsPerWord; + if (pr_sidx != UINT32_MAX) { + nonref_word |= info_pr_here << variant_idx_lowbits; + if (variant_idx_lowbits == (kBitsPerWord - 1)) { + *nonref_flags_iter++ = nonref_word; + nonref_word = 0; + } + } + if (sample_ct && (!phase_or_dosage_found)) { + // Check if there's at least one phased het call, and/or at least one + // relevant dosage. + // Don't bother multithreading this since it's trivial. + if (dosage_start || hds_start) { + if (n_allele == 2) { + bcf_parse_err = BcfScanBiallelicHds(&(ctx.bic), gt_start, qual_starts, dosage_start, hds_start, &phase_or_dosage_found, sample_nypbuf); + } else { + putc_unlocked('\n', stdout); + logerrputs("Error: --bcf multiallelic dosage import is under development.\n"); + reterr = kPglRetNotYetSupported; + goto BcfToPgen_ret_1; + } + } else if (gt_start) { + // scan for phase + bcf_parse_err = BcfScanGt(&(ctx.bic), gt_start, qual_starts, &phase_or_dosage_found, sample_nypbuf); + } + if (unlikely(bcf_parse_err)) { + goto BcfToPgen_ret_PARSE; + } + } + if (unlikely(variant_ct++ == max_variant_ct)) { +#ifdef __LP64__ + if (variant_ct == 0x7ffffffd) { + putc_unlocked('\n', stdout); + logerrputs("Error: " PROG_NAME_STR " does not support more than 2^31 - 3 variants. We recommend using\nother software for very deep studies of small numbers of genomes.\n"); + goto BcfToPgen_ret_MALFORMED_INPUT; + } +#endif + goto BcfToPgen_ret_NOMEM; + } + if (!(variant_ct % 10000)) { + printf("\r--bcf: %uk variants scanned.", variant_ct / 1000); + fflush(stdout); + } + } + if (variant_ct % kBitsPerWord) { + if (nonref_flags_iter) { + *nonref_flags_iter = nonref_word; + } + } else if (unlikely(!variant_ct)) { + logerrputs("Error: No variants in --bcf file.\n"); + goto BcfToPgen_ret_INCONSISTENT_INPUT; + } + + const uintptr_t variant_skip_ct = vrec_idx - 1 - variant_ct; + putc_unlocked('\r', stdout); + if (!variant_skip_ct) { + logprintf("--bcf: %u variant%s scanned.\n", variant_ct, (variant_ct == 1)? "" : "s"); + } else { + logprintf("--bcf: %u variant%s scanned (%" PRIuPTR " skipped).\n", variant_ct, (variant_ct == 1)? "" : "s", variant_skip_ct); + } + + if (allele_idx_end > 2 * variant_ct) { + allele_idx_offsets[variant_ct] = allele_idx_end; + BigstackFinalizeW(allele_idx_offsets, variant_ct + 1); + } else { + allele_idx_offsets = nullptr; + } + + BigstackEndReset(bigstack_end_mark2); + loadbuf_size = RoundUpPow2(loadbuf_size_needed, kEndAllocAlign); + loadbuf = S_CAST(unsigned char*, bigstack_end_alloc_raw(loadbuf_size)); + + reterr = BgzfRawMtStreamRewind(&bgzf, &bgzf_errmsg); + if (unlikely(reterr)) { + if (reterr == kPglRetDecompressFail) { + goto BcfToPgen_ret_REWIND_FAIL; + } + goto BcfToPgen_ret_BGZF_FAIL; + } + { + unsigned char* bcf_header_buf = R_CAST(unsigned char*, &(vcf_header[-9])); + unsigned char* bcf_iter = bcf_header_buf; + unsigned char* bcf_header_end = &(bcf_header_buf[header_size + 9 * k1LU]); + reterr = BgzfRawMtStreamRead(bcf_header_end, &bgzf, &bcf_iter, &bgzf_errmsg); + if (unlikely(reterr)) { + goto BcfToPgen_ret_BGZF_FAIL; + } + if (unlikely(bcf_iter != bcf_header_end)) { + reterr = kPglRetReadFail; + goto BcfToPgen_ret_BGZF_FAIL; + } + } + + uint32_t calc_thread_ct; + // todo: tune this for BCF, these values were derived from VCF testing + if (phase_or_dosage_found && (dosage_sidx || hds_sidx)) { + calc_thread_ct = 1 + (sample_ct > 5) + (sample_ct > 12) + (sample_ct > 32) + (sample_ct > 512); + } else { + calc_thread_ct = 1 + (sample_ct > 40) + (sample_ct > 320); + } + if (calc_thread_ct + decompress_thread_ct > max_thread_ct) { + calc_thread_ct = MAXV(1, max_thread_ct - decompress_thread_ct); + } + + snprintf(outname_end, kMaxOutfnameExtBlen, ".pvar"); + if (unlikely(fopen_checked(outname, FOPEN_WB, &pvarfile))) { + goto BcfToPgen_ret_OPEN_FAIL; + } + line_iter = vcf_header; + uint32_t contig_idx = UINT32_MAX; // deliberate overflow + uint32_t idxeq_clipped = 0; + for (header_line_idx = 1; header_line_idx != header_line_ct; ++header_line_idx) { + char* line_start = line_iter; + char* line_main = &(line_iter[2]); + char* line_end = AdvPastDelim(line_main, '\n'); + line_iter = line_end; + // chrSet skipped here since we call AppendChrsetLine after this loop + if (StrStartsWithUnsafe(line_main, "fileformat=") || StrStartsWithUnsafe(line_main, "fileDate=") || StrStartsWithUnsafe(line_main, "source=") || StrStartsWithUnsafe(line_main, "FORMAT=") || StrStartsWithUnsafe(line_main, "chrSet=")) { + continue; + } + // OS-agnostic newline clip. + char* line_write_end = &(line_end[-1]); + if (line_write_end[-1] == '\r') { + --line_write_end; + } + const uint32_t is_contig_line = StrStartsWithUnsafe(line_main, "contig=". + --line_write_end; + do { + --line_write_end; + } while (IsDigit(*line_write_end)); + line_write_end = &(line_write_end[-4]); + // line_write_end now points to the '=' in 'IDX='. + } + } + if (is_contig_line) { + if (explicit_idx_keys) { + ScanUintDefcap(&(line_write_end[5]), &contig_idx); + } else { + ++contig_idx; + } + if (!IsSet(bcf_contig_keep, contig_idx)) { + continue; + } + } + if (unlikely(fwrite_checked(line_start, line_write_end - line_start, pvarfile))) { + goto BcfToPgen_ret_WRITE_FAIL; + } + if (idxeq_clipped) { + putc_unlocked('>', pvarfile); + } + // NOT safe to use AppendBinaryEoln here. +#ifdef _WIN32 + if (unlikely(fputs_checked("\r\n", pvarfile))) { + goto BcfToPgen_ret_WRITE_FAIL; + } +#else + if (unlikely(putc_checked('\n', pvarfile))) { + goto BcfToPgen_ret_WRITE_FAIL; + } +#endif + } + char* write_iter = g_textbuf; + if (cip->chrset_source) { + AppendChrsetLine(cip, &write_iter); + } + write_iter = strcpya_k(write_iter, "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER"); + if (info_nonpr_present) { + write_iter = strcpya_k(write_iter, "\tINFO"); + } + AppendBinaryEoln(&write_iter); + if (unlikely(fwrite_checked(g_textbuf, write_iter - g_textbuf, pvarfile))) { + goto BcfToPgen_ret_WRITE_FAIL; + } + + const uint32_t variant_ctl = BitCtToWordCt(variant_ct); + PgenGlobalFlags phase_dosage_gflags = kfPgenGlobal0; + GparseFlags gparse_flags = kfGparse0; // yeah, this is a bit redundant + if (phase_or_dosage_found || hds_sidx || dosage_sidx) { + if ((!hds_sidx) && (!dosage_sidx)) { + phase_dosage_gflags = kfPgenGlobalHardcallPhasePresent; + gparse_flags = kfGparseHphase; + } else { + // thanks to automatic --hard-call-threshold, we may need to save + // dosage-phase even when there's no HDS field + phase_dosage_gflags = kfPgenGlobalHardcallPhasePresent | kfPgenGlobalDosagePresent | kfPgenGlobalDosagePhasePresent; + gparse_flags = kfGparseHphase | kfGparseDosage | kfGparseDphase; + } + } + uint32_t nonref_flags_storage = 1; + if (nonref_flags) { + const uint32_t variant_ctl_m1 = variant_ctl - 1; + const uintptr_t last_nonref_flags_word = nonref_flags[variant_ctl_m1]; + if (!last_nonref_flags_word) { + for (uint32_t widx = 0; widx != variant_ctl_m1; ++widx) { + if (nonref_flags[widx]) { + nonref_flags_storage = 3; + break; + } + } + } else if (!((~last_nonref_flags_word) << ((-variant_ct) & (kBitsPerWord - 1)))) { + nonref_flags_storage = 2; + for (uint32_t widx = 0; widx != variant_ctl_m1; ++widx) { + if (~nonref_flags[widx]) { + nonref_flags_storage = 3; + break; + } + } + } else { + nonref_flags_storage = 3; + } + if (nonref_flags_storage != 3) { + // yeah, we may now have a temporary "memory leak" here (if + // multiallelic variants are present, and thus allele_idx_offsets[] + // must be kept around), but this array is typically only 1/64 the size + // of allele_idx_offsets[]. + if (!allele_idx_offsets) { + BigstackReset(nonref_flags); + } + nonref_flags = nullptr; + } + } + char* writebuf; + { + const uint64_t write_chunk_ubound = MAXV(other_slen_ubound, filter_info_slen_ubound); +#ifndef __LP64__ + if (write_chunk_ubound > 0x7ff00000) { + goto BcfToPgen_ret_NOMEM; + } +#endif + if (unlikely(bigstack_alloc_c(write_chunk_ubound + kMaxMediumLine + 32, &writebuf))) { + goto BcfToPgen_ret_NOMEM; + } + } + write_iter = writebuf; + char* writebuf_flush = &(writebuf[kMaxMediumLine]); + + // may as well have a functional progress meter in no-samples case + uint32_t main_block_size = MINV(65536, variant_ct); + uint32_t per_thread_block_limit = main_block_size; + uint32_t cur_thread_block_vidx_limit = 1; + uintptr_t per_thread_byte_limit = 0; + unsigned char* geno_bufs[2]; + // defensive + geno_bufs[0] = nullptr; + geno_bufs[1] = nullptr; + if (hard_call_thresh == UINT32_MAX) { + hard_call_thresh = kDosageMid / 10; + } + const uint32_t hard_call_halfdist = kDosage4th - hard_call_thresh; + if (sample_ct) { + snprintf(outname_end, kMaxOutfnameExtBlen, ".pgen"); + uintptr_t spgw_alloc_cacheline_ct; + uint32_t max_vrec_len; + reterr = SpgwInitPhase1(outname, allele_idx_offsets, nonref_flags, variant_ct, sample_ct, phase_dosage_gflags, nonref_flags_storage, &spgw, &spgw_alloc_cacheline_ct, &max_vrec_len); + if (unlikely(reterr)) { + if (reterr == kPglRetOpenFail) { + logerrprintfww(kErrprintfFopen, outname, strerror(errno)); + } + goto BcfToPgen_ret_1; + } + unsigned char* spgw_alloc; + if (unlikely(bigstack_alloc_uc(spgw_alloc_cacheline_ct * kCacheline, &spgw_alloc))) { + goto BcfToPgen_ret_NOMEM; + } + SpgwInitPhase2(max_vrec_len, &spgw, spgw_alloc); + + if (unlikely( + bigstack_alloc_ucp(calc_thread_ct, &ctx.thread_wkspaces) || + bigstack_alloc_u32(calc_thread_ct + 1, &(ctx.thread_bidxs[0])) || + bigstack_alloc_u32(calc_thread_ct + 1, &(ctx.thread_bidxs[1])) || + bigstack_calloc_w(calc_thread_ct, &ctx.err_vrec_idxs))) { + goto BcfToPgen_ret_NOMEM; + } + ctx.bcf_parse_errs = S_CAST(BcfParseErr*, bigstack_alloc_raw_rd(calc_thread_ct * sizeof(BcfParseErr))); + if (unlikely(!ctx.bcf_parse_errs)) { + goto BcfToPgen_ret_NOMEM; + } + ctx.hard_call_halfdist = hard_call_halfdist; + ctx.parse_failed = 0; + // defensive + ctx.gparse[0] = nullptr; + ctx.gparse[1] = nullptr; + ctx.block_allele_idx_offsets[0] = nullptr; + ctx.block_allele_idx_offsets[1] = nullptr; + // Finished with all other memory allocations, so all remaining workspace + // can be spent on multithreaded parsing. Spend up to 1/6 on + // g_thread_wkspaces (tune this fraction later). + // Probable todo: factor out common parts with bgen-1.3 initialization + // into separate function(s). + uint64_t max_write_byte_ct = GparseWriteByteCt(sample_ct, max_allele_ct, gparse_flags); + // always allocate tmp_dphase_delta for now + uint64_t thread_wkspace_cl_ct = DivUp(max_write_byte_ct + sample_ct * sizeof(SDosage), kCacheline); + uintptr_t cachelines_avail = bigstack_left() / (6 * kCacheline); + if (calc_thread_ct * thread_wkspace_cl_ct > cachelines_avail) { + if (unlikely(thread_wkspace_cl_ct > cachelines_avail)) { + goto BcfToPgen_ret_NOMEM; + } + calc_thread_ct = cachelines_avail / thread_wkspace_cl_ct; + } + for (uint32_t tidx = 0; tidx != calc_thread_ct; ++tidx) { + ctx.thread_wkspaces[tidx] = S_CAST(unsigned char*, bigstack_alloc_raw(thread_wkspace_cl_ct * kCacheline)); + ctx.bcf_parse_errs[tidx] = kBcfParseOk; + } + + // be pessimistic re: rounding + cachelines_avail = (bigstack_left() / kCacheline) - 4; + const uint64_t max_bytes_req_per_variant = sizeof(GparseRecord) + MAXV(max_observed_rec_vecs * S_CAST(uintptr_t, kBytesPerVec), max_write_byte_ct) + calc_thread_ct; + if (unlikely(cachelines_avail * kCacheline < 2 * max_bytes_req_per_variant)) { + goto BcfToPgen_ret_NOMEM; + } + // use worst-case gparse_flags since lines will usually be similar + uintptr_t min_bytes_req_per_variant = sizeof(GparseRecord) + GparseWriteByteCt(sample_ct, 2, gparse_flags); + main_block_size = (cachelines_avail * kCacheline) / (min_bytes_req_per_variant * 2); + // this is arbitrary, there's no connection to kPglVblockSize + if (main_block_size > 65536) { + main_block_size = 65536; + } + // divide by 2 for better parallelism in small-variant-count case + // round up per_thread_block_limit so we only have two blocks + if (main_block_size > DivUp(variant_ct, 2)) { + main_block_size = DivUp(variant_ct, 2) + calc_thread_ct - 1; + } + // may as well guarantee divisibility + per_thread_block_limit = main_block_size / calc_thread_ct; + main_block_size = per_thread_block_limit * calc_thread_ct; + if (unlikely(SetThreadCt(calc_thread_ct, &tg))) { + goto BcfToPgen_ret_NOMEM; + } + ctx.gparse[0] = S_CAST(GparseRecord*, bigstack_alloc_raw_rd(main_block_size * sizeof(GparseRecord))); + ctx.gparse[1] = S_CAST(GparseRecord*, bigstack_alloc_raw_rd(main_block_size * sizeof(GparseRecord))); + SetThreadFuncAndData(BcfGenoToPgenThread, &ctx, &tg); + cachelines_avail = bigstack_left() / (kCacheline * 2); + geno_bufs[0] = S_CAST(unsigned char*, bigstack_alloc_raw(cachelines_avail * kCacheline)); + geno_bufs[1] = S_CAST(unsigned char*, bigstack_alloc_raw(cachelines_avail * kCacheline)); + // This is only used for comparison purposes, so it is unnecessary to + // round it down to a multiple of kBytesPerVec even though every actual + // record will be vector-aligned. + per_thread_byte_limit = (cachelines_avail * kCacheline) / calc_thread_ct; + } + + vrec_idx = 0; + // Main workflow: + // 1. Set n=0, load genotype data for first main_block_size variants + // while writing .pvar + // + // 2. Spawn threads processing batch n genotype data + // 3. If n>0, write results for block (n-1) + // 4. Increment n by 1 + // 5. Load/write-.pvar for batch (n+1) unless eof + // 6. Join threads + // 7. Goto step 2 unless eof + // + // 8. Write results for last block + uint32_t prev_block_write_ct = 0; + uintptr_t record_byte_ct = 0; + uint32_t* thread_bidxs = nullptr; + GparseRecord* cur_gparse = nullptr; + unsigned char* geno_buf_iter = nullptr; + unsigned char* cur_thread_byte_stop = nullptr; + + // Placed here since these values need to persist to the next block + // iteration when we run out of geno_bufs[parity] space. + uint32_t chrom = 0; + uint32_t pos = 0; + uint32_t qual_bits = 0; + uint32_t n_allele = 0; + uint32_t n_info = 0; + uint32_t n_fmt = 0; + const unsigned char* shared_end = nullptr; + + const unsigned char* gt_start = nullptr; + const unsigned char* qual_starts[2]; + qual_starts[0] = nullptr; + qual_starts[1] = nullptr; + const unsigned char* dosage_start = nullptr; + const unsigned char* hds_start = nullptr; + uint32_t gt_type_blen = 0; + uint32_t gt_main_blen = 0; + uint32_t qual_type_blens[2]; + uint32_t qual_main_blens[2]; + qual_type_blens[0] = 0; + qual_type_blens[1] = 0; + qual_main_blens[0] = 0; + qual_main_blens[1] = 0; + uint32_t dosage_type_blen = 0; + uint32_t dosage_main_blen = 0; + uint32_t hds_type_blen = 0; + uint32_t hds_main_blen = 0; + uint32_t record_input_vec_ct = 0; + + uint32_t parity = 0; + for (uint32_t vidx_start = 0; ; ) { + uint32_t cur_block_write_ct = 0; + if (!IsLastBlock(&tg)) { + const uint32_t block_vidx_limit = variant_ct - vidx_start; + cur_thread_block_vidx_limit = MINV(block_vidx_limit, per_thread_block_limit); + uint32_t cur_thread_fill_idx = 0; + if (sample_ct) { + thread_bidxs = ctx.thread_bidxs[parity]; + cur_gparse = ctx.gparse[parity]; + if (allele_idx_offsets) { + ctx.block_allele_idx_offsets[parity] = &(SpgwGetAlleleIdxOffsets(&spgw)[vidx_start]); + } + geno_buf_iter = geno_bufs[parity]; + cur_thread_byte_stop = &(geno_buf_iter[per_thread_byte_limit]); + thread_bidxs[0] = 0; + } + uint32_t block_vidx = 0; + uint32_t sidx = 0; + GparseRecord* grp; + if (record_input_vec_ct) { + // If the last block iteration ended due to insufficient space in + // geno_bufs[parity], we haven't actually written the current .pvar + // line or copied genotype/quality data over. + goto BcfToPgen_load_keep; + } + while (1) { + { + ++vrec_idx; + unsigned char* loadbuf_read_iter = loadbuf; + reterr = BgzfRawMtStreamRead(&(loadbuf[32]), &bgzf, &loadbuf_read_iter, &bgzf_errmsg); + if (unlikely(reterr)) { + goto BcfToPgen_ret_BGZF_FAIL_N; + } + if (&(loadbuf[32]) != loadbuf_read_iter) { + goto BcfToPgen_ret_REWIND_FAIL_N; + } + // [0]: l_shared + // [1]: l_indiv + // [2]: chrom + // [3]: pos + // [4]: rlen + // [5]: qual, NOT n_allele_info + // [6]: low 16 bits n_info, high 16 bits n_allele + // [7]: low 24 bits n_sample, high 8 bits n_fmt + const uint32_t* vrec_header = R_CAST(uint32_t*, loadbuf); + const uint32_t l_shared = vrec_header[0]; + const uint32_t l_indiv = vrec_header[1]; + chrom = vrec_header[2]; + pos = vrec_header[3]; + qual_bits = vrec_header[5]; + n_allele = vrec_header[6] >> 16; + n_info = vrec_header[6] & 0xffff; + n_fmt = vrec_header[7] >> 24; + // skip validation performed in first pass + const uint64_t second_load_size = l_shared + S_CAST(uint64_t, l_indiv) - 24; + loadbuf_read_iter = loadbuf; + unsigned char* indiv_end = &(loadbuf[second_load_size]); + reterr = BgzfRawMtStreamRead(indiv_end, &bgzf, &loadbuf_read_iter, &bgzf_errmsg); + if (unlikely(reterr)) { + goto BcfToPgen_ret_BGZF_FAIL_N; + } + if (unlikely(indiv_end != loadbuf_read_iter)) { + goto BcfToPgen_ret_REWIND_FAIL_N; + } + + // 1. check if we skip this variant. chromosome filter and + // require_gt can cause this. + if (!IsSet(bcf_contig_keep, chrom)) { + continue; + } + // obvious todo: move duplicated code between first and second pass + // into separate functions + shared_end = indiv_end - l_indiv; + const unsigned char* parse_iter = shared_end; + + gt_start = nullptr; + qual_starts[0] = nullptr; + qual_starts[1] = nullptr; + dosage_start = nullptr; + hds_start = nullptr; + record_input_vec_ct = 0; + uint32_t gt_exists = 0; + for (uint32_t fmt_idx = 0; fmt_idx != n_fmt; ++fmt_idx) { + // 1. typed int indicating which FORMAT field + // 2. shared type descriptor for each entry (usually a single + // byte) + // 3. sample_ct entries + // previously validated + ScanBcfTypedInt(&parse_iter, &sidx); + const unsigned char* type_start = parse_iter; + uint32_t value_type; + uint32_t value_ct; + ScanBcfType(&parse_iter, &value_type, &value_ct); + const uint32_t bytes_per_elem = kBcfBytesPerElem[value_type]; + const uint32_t vec_byte_ct = bytes_per_elem * value_ct * sample_ct; + const uint32_t type_blen = parse_iter - type_start; + parse_iter = &(parse_iter[vec_byte_ct]); + if ((!sidx) || (!value_ct)) { + if (sidx == gt_sidx) { + gt_exists = 1; + } + continue; + } + if (sidx == gt_sidx) { + gt_start = type_start; + gt_type_blen = type_blen; + gt_main_blen = vec_byte_ct; + } else if (sidx == gq_sidx) { + qual_starts[0] = type_start; + qual_type_blens[0] = type_blen; + qual_main_blens[0] = vec_byte_ct; + } else if (sidx == dp_sidx) { + qual_starts[1] = type_start; + qual_type_blens[1] = type_blen; + qual_main_blens[1] = vec_byte_ct; + } else if (sidx == dosage_sidx) { + dosage_start = type_start; + dosage_type_blen = type_blen; + dosage_main_blen = vec_byte_ct; + } else if (sidx == hds_sidx) { + hds_start = type_start; + hds_type_blen = type_blen; + hds_main_blen = vec_byte_ct; + } else { + continue; + } +#ifdef __LP64__ + ++record_input_vec_ct; +#else + record_input_vec_ct += DivUp(type_blen, kBytesPerVec); +#endif + record_input_vec_ct += DivUp(vec_byte_ct, kBytesPerVec); + } + if (require_gt && (!gt_exists)) { + continue; + } + // We already have enough information to determine + // write_byte_ct_limit. + if ((!gt_start) && (!dosage_sidx) && (!hds_sidx)) { + gparse_flags = kfGparseNull; + } else { + if ((!phase_or_dosage_found) && (!dosage_sidx) && (!hds_sidx)) { + gparse_flags = kfGparse0; + } else { + gparse_flags = (dosage_start || hds_start)? (kfGparseHphase | kfGparseDosage | kfGparseDphase) : kfGparseHphase; + } + } + const uintptr_t write_byte_ct_limit = GparseWriteByteCt(sample_ct, n_allele, gparse_flags); + record_byte_ct = MAXV(record_input_vec_ct * S_CAST(uintptr_t, kBytesPerVec), write_byte_ct_limit); + if ((block_vidx == cur_thread_block_vidx_limit) || (S_CAST(uintptr_t, cur_thread_byte_stop - geno_buf_iter) < record_byte_ct)) { + thread_bidxs[++cur_thread_fill_idx] = block_vidx; + if (cur_thread_fill_idx == calc_thread_ct) { + break; + } + cur_thread_byte_stop = &(cur_thread_byte_stop[per_thread_byte_limit]); + cur_thread_block_vidx_limit = MINV(cur_thread_block_vidx_limit + per_thread_block_limit, block_vidx_limit); + } + } + BcfToPgen_load_keep: + // CHROM, POS + write_iter = memcpyax(write_iter, contig_names[chrom], contig_slens[chrom], '\t'); + write_iter = u32toa_x(pos + 1, '\t', write_iter); + if (unlikely(fwrite_ck(writebuf_flush, pvarfile, &write_iter))) { + goto BcfToPgen_ret_WRITE_FAIL; + } + + // ID + const unsigned char* parse_iter = loadbuf; + uint32_t slen; + { + const char* id_start; + ScanBcfTypedString(shared_end, &parse_iter, &id_start, &slen); + if (slen) { + write_iter = memcpya(write_iter, id_start, slen); + } else { + *write_iter++ = '.'; + } + *write_iter++ = '\t'; + if (unlikely(fwrite_ck(writebuf_flush, pvarfile, &write_iter))) { + goto BcfToPgen_ret_WRITE_FAIL; + } + + // REF + const char* ref_start; + ScanBcfTypedString(shared_end, &parse_iter, &ref_start, &slen); + write_iter = memcpyax(write_iter, ref_start, slen, '\t'); + + // ALT + if (n_allele == 1) { + *write_iter++ = '.'; + } else { + for (uint32_t allele_idx = 1; allele_idx != n_allele; ++allele_idx) { + if (unlikely(fwrite_ck(writebuf_flush, pvarfile, &write_iter))) { + goto BcfToPgen_ret_WRITE_FAIL; + } + const char* cur_alt_start; + ScanBcfTypedString(shared_end, &parse_iter, &cur_alt_start, &slen); + write_iter = memcpyax(write_iter, cur_alt_start, slen, ','); + } + --write_iter; + } + *write_iter++ = '\t'; + + // QUAL + if (S_CAST(int32_t, qual_bits) >= 0x7f800000) { + // NaN or missing + *write_iter++ = '.'; + } else { + float qual_f; + memcpy(&qual_f, &qual_bits, 4); + write_iter = ftoa_g(qual_f, write_iter); + } + *write_iter++ = '\t'; + if (unlikely(fwrite_ck(writebuf_flush, pvarfile, &write_iter))) { + goto BcfToPgen_ret_WRITE_FAIL; + } + + // FILTER + uint32_t value_type; + uint32_t value_ct; + ScanBcfType(&parse_iter, &value_type, &value_ct); + if (!value_ct) { + *write_iter++ = '.'; + } else { + const uint32_t value_type_m1 = value_type - 1; + if (!value_type_m1) { + for (uint32_t filter_idx = 0; filter_idx != value_ct; ++filter_idx) { + const uint32_t cur_sidx = parse_iter[filter_idx]; + if (cur_sidx == 0x80) { + *write_iter++ = '.'; + } else { + write_iter = memcpya(write_iter, fif_strings[cur_sidx], fif_slens[cur_sidx]); + } + *write_iter++ = ';'; + } + } else if (value_type_m1 == 1) { + const uint16_t* filter_vec_alias = R_CAST(const uint16_t*, parse_iter); + for (uint32_t filter_idx = 0; filter_idx != value_ct; ++filter_idx) { + const uint32_t cur_sidx = filter_vec_alias[filter_idx]; + if (cur_sidx == 0x8000) { + *write_iter++ = '.'; + } else { + write_iter = memcpya(write_iter, fif_strings[cur_sidx], fif_slens[cur_sidx]); + } + *write_iter++ = ';'; + } + } else { + const uint32_t* filter_vec_alias = R_CAST(const uint32_t*, parse_iter); + for (uint32_t filter_idx = 0; filter_idx != value_ct; ++filter_idx) { + const uint32_t cur_sidx = filter_vec_alias[filter_idx]; + if (cur_sidx == 0x80000000U) { + *write_iter++ = '.'; + } else { + write_iter = memcpya(write_iter, fif_strings[cur_sidx], fif_slens[cur_sidx]); + } + *write_iter++ = ';'; + } + } + parse_iter = &(parse_iter[value_ct << value_type_m1]); + --write_iter; + } + *write_iter++ = '\t'; + if (unlikely(fwrite_ck(writebuf_flush, pvarfile, &write_iter))) { + goto BcfToPgen_ret_WRITE_FAIL; + } + + // INFO + if (!n_info) { + *write_iter++ = '.'; + } else { + for (uint32_t info_idx = 0; info_idx != n_info; ++info_idx) { + ScanBcfTypedInt(&parse_iter, &sidx); + write_iter = strcpya(write_iter, fif_strings[sidx]); + + ScanBcfType(&parse_iter, &value_type, &value_ct); + const uint32_t bytes_per_elem = kBcfBytesPerElem[value_type]; + const uint32_t vec_byte_ct = bytes_per_elem * value_ct; + const unsigned char* cur_vec_start = parse_iter; + parse_iter = &(parse_iter[vec_byte_ct]); + if (!IsSet(info_flags, sidx)) { + // value_ct guaranteed to be positive + *write_iter++ = '='; + // ugh. not a separate function for now since no other code + // needs to do this + if (value_type == 7) { + // string + // Unlike most other VCF/BCF fields, spaces are actually + // allowed by the VCF spec here, so we need to detect them. + // We error out on them for now (possible todo: + // autoconversion to "%20"). + if (unlikely(memchr(cur_vec_start, ' ', value_ct))) { + snprintf(g_logbuf, kLogbufSize, "Error: INFO field in variant record #%" PRIuPTR " of --bcf file contains a space; this cannot be imported by " PROG_NAME_STR ". Remove or reformat the field before reattempting import.\n", vrec_idx); + goto BcfToPgen_ret_MALFORMED_INPUT_WWN; + } + write_iter = memcpya(write_iter, cur_vec_start, value_ct); + } else { + if (value_type == 1) { + // int8 + for (uint32_t value_idx = 0; value_idx != value_ct; ++value_idx) { + const int8_t cur_val = S_CAST(int8_t, cur_vec_start[value_idx]); + if (cur_val != -128) { + write_iter = i32toa(cur_val, write_iter); + } else { + *write_iter++ = '.'; + } + *write_iter++ = ','; + } + } else if (value_type == 2) { + // int16 + const int16_t* cur_vec_alias = R_CAST(const int16_t*, cur_vec_start); + for (uint32_t value_idx = 0; value_idx != value_ct; ++value_idx) { + const int16_t cur_val = cur_vec_alias[value_idx]; + if (cur_val != -32768) { + write_iter = i32toa(cur_val, write_iter); + } else { + *write_iter++ = '.'; + } + *write_iter++ = ','; + } + } else if (value_type == 3) { + // int32 + const int32_t* cur_vec_alias = R_CAST(const int32_t*, cur_vec_start); + for (uint32_t value_idx = 0; value_idx != value_ct; ++value_idx) { + const int32_t cur_val = cur_vec_alias[value_idx]; + if (cur_val != -2147483648) { + write_iter = i32toa(cur_val, write_iter); + } else { + *write_iter++ = '.'; + } + *write_iter++ = ','; + } + } else { + // float + const uint32_t* cur_vec_alias = R_CAST(const uint32_t*, cur_vec_start); + for (uint32_t value_idx = 0; value_idx != value_ct; ++value_idx) { + uint32_t cur_bits = cur_vec_alias[value_idx]; + if (cur_bits != 0x7f800001) { + float cur_float; + memcpy(&cur_float, &cur_bits, 4); + write_iter = ftoa_g(cur_float, write_iter); + } else { + *write_iter++ = '.'; + } + *write_iter++ = ','; + } + } + --write_iter; + } + } + *write_iter++ = ';'; + } + --write_iter; + } + AppendBinaryEoln(&write_iter); + if (unlikely(fwrite_ck(writebuf_flush, pvarfile, &write_iter))) { + goto BcfToPgen_ret_WRITE_FAIL; + } + if (!sample_ct) { + if (++block_vidx == cur_thread_block_vidx_limit) { + break; + } + continue; + } + } + + grp = &(cur_gparse[block_vidx]); + grp->record_start = geno_buf_iter; + grp->flags = gparse_flags; + grp->metadata.read_bcf.qual_vec_offsets[0] = UINT32_MAX; + grp->metadata.read_bcf.qual_vec_offsets[1] = UINT32_MAX; + grp->metadata.read_bcf.gt_vec_offset = UINT32_MAX; + grp->metadata.read_bcf.dosage_vec_offset = UINT32_MAX; + grp->metadata.read_bcf.hds_vec_offset = UINT32_MAX; + grp->metadata.read_bcf.rec_idx = vrec_idx; + uintptr_t copy_vec_offset = 0; + for (uint32_t qual_idx = 0; qual_idx != 2; ++qual_idx) { + if (qual_starts[qual_idx]) { + grp->metadata.read_bcf.qual_vec_offsets[qual_idx] = copy_vec_offset; + const uint32_t type_blen = qual_type_blens[qual_idx]; + const unsigned char* src_iter = qual_starts[qual_idx]; + memcpy(&(geno_buf_iter[copy_vec_offset * kBytesPerVec]), src_iter, type_blen); + src_iter = &(src_iter[type_blen]); +#ifdef __LP64__ + ++copy_vec_offset; +#else + copy_vec_offset += DivUp(type_blen, kBytesPerVec); +#endif + const uint32_t vec_blen = qual_main_blens[qual_idx]; + memcpy(&(geno_buf_iter[copy_vec_offset * kBytesPerVec]), src_iter, vec_blen); + copy_vec_offset += DivUp(vec_blen, kBytesPerVec); + } + } + if (gt_start) { + grp->metadata.read_bcf.gt_vec_offset = copy_vec_offset; + const unsigned char* src_iter = gt_start; + memcpy(&(geno_buf_iter[copy_vec_offset * kBytesPerVec]), src_iter, gt_type_blen); + src_iter = &(src_iter[gt_type_blen]); +#ifdef __LP64__ + ++copy_vec_offset; +#else + copy_vec_offset += DivUp(gt_type_blen, kBytesPerVec); +#endif + memcpy(&(geno_buf_iter[copy_vec_offset * kBytesPerVec]), src_iter, gt_main_blen); + copy_vec_offset += DivUp(gt_main_blen, kBytesPerVec); + } + if (dosage_start) { + grp->metadata.read_bcf.dosage_vec_offset = copy_vec_offset; + const unsigned char* src_iter = dosage_start; + memcpy(&(geno_buf_iter[copy_vec_offset * kBytesPerVec]), src_iter, dosage_type_blen); + src_iter = &(src_iter[dosage_type_blen]); +#ifdef __LP64__ + ++copy_vec_offset; +#else + copy_vec_offset += DivUp(dosage_type_blen, kBytesPerVec); +#endif + memcpy(&(geno_buf_iter[copy_vec_offset * kBytesPerVec]), src_iter, dosage_main_blen); + copy_vec_offset += DivUp(dosage_main_blen, kBytesPerVec); + } + if (hds_start) { + grp->metadata.read_bcf.hds_vec_offset = copy_vec_offset; + const unsigned char* src_iter = hds_start; + memcpy(&(geno_buf_iter[copy_vec_offset * kBytesPerVec]), src_iter, hds_type_blen); + src_iter = &(src_iter[hds_type_blen]); +#ifdef __LP64__ + ++copy_vec_offset; +#else + copy_vec_offset += DivUp(hds_type_blen, kBytesPerVec); +#endif + memcpy(&(geno_buf_iter[copy_vec_offset * kBytesPerVec]), src_iter, hds_main_blen); + // copy_vec_offset += DivUp(hds_main_blen, kBytesPerVec); + } + geno_buf_iter = &(geno_buf_iter[record_byte_ct]); + ++block_vidx; + + // true iff this is the last variant we're keeping in the entire + // file + if (block_vidx == block_vidx_limit) { + for (; cur_thread_fill_idx != calc_thread_ct; ) { + // save endpoint for current thread, and tell any leftover + // threads to do nothing + thread_bidxs[++cur_thread_fill_idx] = block_vidx; + } + break; + } + } + cur_block_write_ct = block_vidx; + } + if (sample_ct) { + if (vidx_start) { + JoinThreads(&tg); + if (unlikely(ctx.parse_failed)) { + goto BcfToPgen_ret_THREAD_PARSE; + } + } + if (!IsLastBlock(&tg)) { + if (vidx_start + cur_block_write_ct == variant_ct) { + DeclareLastThreadBlock(&tg); + } + if (unlikely(SpawnThreads(&tg))) { + goto BcfToPgen_ret_THREAD_CREATE_FAIL; + } + } + parity = 1 - parity; + if (vidx_start) { + // write *previous* block results + reterr = GparseFlush(ctx.gparse[parity], prev_block_write_ct, &spgw); + if (unlikely(reterr)) { + goto BcfToPgen_ret_1; + } + } + } else if (vidx_start + cur_block_write_ct == variant_ct) { + break; + } + if (vidx_start == variant_ct) { + break; + } + if (vidx_start) { + printf("\r--bcf: %uk variants converted.", vidx_start / 1000); + if (vidx_start <= main_block_size) { + fputs(" \b\b\b\b", stdout); + } + fflush(stdout); + } + vidx_start += cur_block_write_ct; + prev_block_write_ct = cur_block_write_ct; + } + if (unlikely(fclose_flush_null(writebuf_flush, write_iter, &pvarfile))) { + goto BcfToPgen_ret_WRITE_FAIL; + } + if (sample_ct) { + SpgwFinish(&spgw); + } + putc_unlocked('\r', stdout); + write_iter = strcpya_k(g_logbuf, "--bcf: "); + const uint32_t outname_base_slen = outname_end - outname; + if (sample_ct) { + write_iter = memcpya(write_iter, outname, outname_base_slen + 5); + write_iter = strcpya_k(write_iter, " + "); + } else { + *pgen_generated_ptr = 0; + } + write_iter = memcpya(write_iter, outname, outname_base_slen); + write_iter = strcpya_k(write_iter, ".pvar"); + if (sample_ct && (!preexisting_psamname)) { + write_iter = strcpya_k(write_iter, " + "); + write_iter = memcpya(write_iter, outname, outname_base_slen); + write_iter = strcpya_k(write_iter, ".psam"); + } else { + *psam_generated_ptr = 0; + } + write_iter = strcpya_k(write_iter, " written"); + if (!sample_ct) { + write_iter = strcpya_k(write_iter, " (no samples present)"); + } + strcpy_k(write_iter, ".\n"); + WordWrapB(0); + logputsb(); + } + while (0) { + BcfToPgen_ret_NOMEM: + reterr = kPglRetNomem; + break; + BcfToPgen_ret_OPEN_FAIL: + reterr = kPglRetOpenFail; + break; + BcfToPgen_ret_WRITE_FAIL: + reterr = kPglRetWriteFail; + break; + BcfToPgen_ret_BGZF_FAIL_N: + putc_unlocked('\n', stdout); + BcfToPgen_ret_BGZF_FAIL: + // ReadFail, DecompressFail, and ThreadCreateFail possible. + if (reterr == kPglRetReadFail) { + logerrprintfww(kErrprintfFread, bcfname, strerror(errno)); + } else if (reterr == kPglRetDecompressFail) { + logerrprintfww(kErrprintfDecompress, bcfname, bgzf_errmsg); + } + break; + BcfToPgen_ret_REWIND_FAIL_N: + putc_unlocked('\n', stdout); + BcfToPgen_ret_REWIND_FAIL: + logerrprintfww(kErrprintfRewind, "--bcf file"); + reterr = kPglRetRewindFail; + break; + BcfToPgen_ret_THREAD_PARSE: + { + // Doesn't really cost us anything to report the first error if multiple + // converter threads error out in the same block, though we can't + // generally guarantee that we'll report the first error in the file. + for (uint32_t tidx = 0; ; ++tidx) { + bcf_parse_err = ctx.bcf_parse_errs[tidx]; + if (bcf_parse_err) { + vrec_idx = ctx.err_vrec_idxs[tidx]; + break; + } + } + } + BcfToPgen_ret_PARSE: + if (bcf_parse_err == kBcfParseHalfCallError) { + putc_unlocked('\n', stdout); + logerrprintf("Error: Variant record #%" PRIuPTR " of --bcf file has a GT half-call.\n", vrec_idx); + if (!half_call_explicit_error) { + logerrputs("Use --vcf-half-call to specify how these should be processed.\n"); + } + reterr = kPglRetMalformedInput; + break; + } else if (bcf_parse_err == kBcfParseInvalidDosage) { + putc_unlocked('\n', stdout); + logerrprintfww("Error: Variant record #%" PRIuPTR " of --bcf file has an invalid %s field.\n", vrec_idx, dosage_import_field); + reterr = kPglRetInconsistentInput; + break; + } else if (bcf_parse_err == kBcfParseWideGt) { + putc_unlocked('\n', stdout); + logerrprintfww("Error: Variant record #%" PRIuPTR " of --bcf file uses unexpectedly-wide integers for the GT field; only int8s are currently supported for n_allele < 64, and int16s for n_allele in [64, 16383].\n", vrec_idx); + reterr = kPglRetNotYetSupported; + break; + } else if (bcf_parse_err == kBcfParseFloatDp) { + putc_unlocked('\n', stdout); + logerrprintfww("Error: Variant record #%" PRIuPTR " of --bcf file has a floating-point DP field; only integers are supported for now.\n", vrec_idx); + reterr = kPglRetNotYetSupported; + break; + } else if (bcf_parse_err == kBcfParseNonfloatDosage) { + putc_unlocked('\n', stdout); + logerrprintfww("Error: Variant record #%" PRIuPTR " of --bcf file has a dosage field that isn't of Float type; this isn't currently supported.\n", vrec_idx); + reterr = kPglRetNotYetSupported; + break; + } + // kBcfParseMalformedGeneric + BcfToPgen_ret_VREC_GENERIC: + putc_unlocked('\n', stdout); + logerrprintf("Error: Variant record #%" PRIuPTR " of --bcf file is malformed.\n", vrec_idx); + reterr = kPglRetMalformedInput; + break; + BcfToPgen_ret_MALFORMED_INPUT_2: + logerrputsb(); + BcfToPgen_ret_MALFORMED_INPUT: + reterr = kPglRetMalformedInput; + break; + BcfToPgen_ret_MALFORMED_INPUT_WWN: + putc_unlocked('\n', stdout); + BcfToPgen_ret_MALFORMED_INPUT_WW: + WordWrapB(0); + logerrputsb(); + reterr = kPglRetMalformedInput; + break; + BcfToPgen_ret_MALFORMED_INPUT_GENERIC: + logerrputs("Error: Malformed BCF file.\n"); + reterr = kPglRetMalformedInput; + break; + BcfToPgen_ret_MALFORMED_TEXT_HEADER: + logerrputs("Error: Malformed BCF text header block.\n"); + reterr = kPglRetMalformedInput; + break; + BcfToPgen_ret_INCONSISTENT_INPUT: + reterr = kPglRetInconsistentInput; + break; + BcfToPgen_ret_THREAD_CREATE_FAIL: + reterr = kPglRetThreadCreateFail; + break; + } + BcfToPgen_ret_1: + CleanupSpgw(&spgw, &reterr); + CleanupThreads(&tg); + fclose_cond(pvarfile); + CleanupBgzfRawMtStream(&bgzf); + fclose_cond(bcffile); + BigstackDoubleReset(bigstack_mark, bigstack_end_mark); + return reterr; +} + + +PglErr OxSampleToPsam(const char* samplename, const char* ox_missing_code, ImportFlags import_flags, char* outname, char* outname_end, uint32_t* sample_ct_ptr) { + unsigned char* bigstack_mark = g_bigstack_base; + FILE* psamfile = nullptr; + PglErr reterr = kPglRetSuccess; + uintptr_t line_idx = 0; + TextStream sample_txs; + PreinitTextStream(&sample_txs); + { + uint32_t omp_slen = 2; + char output_missing_pheno[kMaxMissingPhenostrBlen]; + if (import_flags & kfImportKeepAutoconv) { + // must use --output-missing-phenotype parameter, which we've validated + // to be consistent with --input-missing-phenotype + omp_slen = strlen(g_output_missing_pheno); + memcpy(output_missing_pheno, g_output_missing_pheno, omp_slen); + } else { + // use "NA" since that's always safe + memcpy_k(output_missing_pheno, "NA", 2); + } + const char* missing_catname = g_missing_catname; + uint32_t missing_catname_slen = strlen(missing_catname); + + uint32_t max_line_blen; + if (unlikely(StandardizeMaxLineBlen(bigstack_left() / 4, &max_line_blen))) { + goto OxSampleToPsam_ret_NOMEM; + } + reterr = InitTextStream(samplename, max_line_blen, 1, &sample_txs); + if (unlikely(reterr)) { + if (reterr == kPglRetOpenFail) { + const uint32_t slen = strlen(samplename); + if ((!StrEndsWith(samplename, ".sample", slen)) && + (!StrEndsWith(samplename, ".sample.gz", slen))) { + logerrprintfww("Error: Failed to open %s : %s. (--sample expects a complete filename; did you forget '.sample' at the end?)\n", samplename, strerror(errno)); + goto OxSampleToPsam_ret_1; + } + } + goto OxSampleToPsam_ret_TSTREAM_FAIL; + } + uint32_t mc_ct = 0; + uintptr_t max_mc_blen = 1; + char* sorted_mc = nullptr; + if (!ox_missing_code) { + if (unlikely(bigstack_alloc_c(3, &sorted_mc))) { + goto OxSampleToPsam_ret_NOMEM; + } + strcpy_k(sorted_mc, "NA"); + mc_ct = 1; + max_mc_blen = 3; + } else { + // er, this should use something like + // CountAndMeasureMultistrReverseAlloc()... + const char* missing_code_iter = ox_missing_code; + while (*missing_code_iter) { + while (*missing_code_iter == ',') { + ++missing_code_iter; + } + if (!(*missing_code_iter)) { + break; + } + ++mc_ct; + const char* token_end = strchrnul(missing_code_iter, ','); + uintptr_t token_slen = token_end - missing_code_iter; + if (token_slen >= max_mc_blen) { + max_mc_blen = token_slen + 1; + } + missing_code_iter = token_end; + } + if (mc_ct) { + if (unlikely(bigstack_alloc_c(mc_ct * max_mc_blen, &sorted_mc))) { + goto OxSampleToPsam_ret_NOMEM; + } + missing_code_iter = ox_missing_code; + for (uintptr_t mc_idx = 0; mc_idx != mc_ct; ++mc_idx) { + while (*missing_code_iter == ',') { + ++missing_code_iter; + } + const char* token_end = strchrnul(missing_code_iter, ','); + uintptr_t token_slen = token_end - missing_code_iter; + memcpyx(&(sorted_mc[mc_idx * max_mc_blen]), missing_code_iter, token_slen, '\0'); + missing_code_iter = token_end; + } + // this was temporarily broken in June 2018 due to first + // strcmp_overread() implementation returning 0 instead of -1 on + // less-than + qsort(sorted_mc, mc_ct, max_mc_blen, strcmp_overread_casted); + } + } + + // New first pass: check whether, from the third line on, all first tokens + // are '0'. If yes, we omit FID from the output. + uint32_t write_fid = 0; + uint32_t header_lines_left = 2; + while (1) { + ++line_idx; + char* line_start = TextGet(&sample_txs); + if (!line_start) { + // bugfix (16 Feb 2018): don't check this if we break out of the loop + // on non-0 FID + if (unlikely(TextStreamErrcode2(&sample_txs, &reterr))) { + goto OxSampleToPsam_ret_TSTREAM_FAIL; + } + if (unlikely(header_lines_left)) { + logerrputs("Error: Empty .sample file.\n"); + goto OxSampleToPsam_ret_MALFORMED_INPUT; + } + break; + } + if (header_lines_left) { + --header_lines_left; + continue; + } + if ((line_start[0] != '0') || (!IsSpaceOrEoln(line_start[1]))) { + write_fid = 1; + break; + } + } + reterr = TextRewind(&sample_txs); + if (unlikely(reterr)) { + goto OxSampleToPsam_ret_TSTREAM_FAIL; + } + line_idx = 1; + char* line_start = TextGet(&sample_txs); + if (unlikely(!line_start)) { + reterr = TextStreamRawErrcode(&sample_txs); + goto OxSampleToPsam_ret_TSTREAM_REWIND_FAIL; } char* token_end = CurTokenEnd(line_start); // todo: allow arbitrary first column name, and ID_2/missing to be absent, @@ -4495,16 +9596,23 @@ goto OxGenToPgen_ret_NOMEM; } const uint32_t decompress_thread_ct = 1 + (max_thread_ct > 2); - reterr = InitTextStream(genname, max_line_blen, decompress_thread_ct, &gen_txs); + reterr = ForceNonFifo(genname); if (unlikely(reterr)) { if (reterr == kPglRetOpenFail) { const uint32_t slen = strlen(genname); if ((!StrEndsWith(genname, ".gen", slen)) && (!StrEndsWith(genname, ".gen.gz", slen))) { logerrprintfww("Error: Failed to open %s : %s. (--gen expects a complete filename; did you forget '.gen' at the end?)\n", genname, strerror(errno)); - goto OxGenToPgen_ret_1; + } else { + logerrprintfww(kErrprintfFopen, genname, strerror(errno)); } + } else { + logerrprintfww(kErrprintfRewind, genname); } + goto OxGenToPgen_ret_1; + } + reterr = InitTextStream(genname, max_line_blen, decompress_thread_ct, &gen_txs); + if (unlikely(reterr)) { goto OxGenToPgen_ret_TSTREAM_FAIL; } const uint32_t allow_extra_chrs = (misc_flags / kfMiscAllowExtraChrs) & 1; @@ -4738,7 +9846,7 @@ } reterr = TextStreamOpenEx(genname, kMaxLongLine, max_line_blen, 1, nullptr, dst, &gen_txs); if (unlikely(reterr)) { - goto OxGenToPgen_ret_TSTREAM_REWIND_FAIL; + goto OxGenToPgen_ret_TSTREAM_FAIL; } } else { reterr = TextRewind(&gen_txs); @@ -4790,7 +9898,7 @@ for (line_idx = 1; line_idx <= line_ct; ++line_idx) { reterr = TextGetUnsafe(&gen_txs, &line_iter); if (unlikely(reterr)) { - goto OxGenToPgen_ret_TSTREAM_REWIND_FAIL; + goto OxGenToPgen_ret_TSTREAM_FAIL; } char* chr_code_str = line_iter; char* chr_code_end = CurTokenEnd(chr_code_str); @@ -4934,9 +10042,6 @@ OxGenToPgen_ret_TSTREAM_FAIL: TextStreamErrPrint(".gen file", &gen_txs); break; - OxGenToPgen_ret_TSTREAM_REWIND_FAIL: - TextStreamErrPrintRewind(".gen file", &gen_txs, &reterr); - break; OxGenToPgen_ret_NOMEM: reterr = kPglRetNomem; break; @@ -5602,7 +10707,7 @@ // Fast paths for common cases. if (bit_precision == 8) { // 1x2 bytes per entry - const uint32_t full_vec_ct = sample_ct / (kBytesPerVec / 2); + const uint32_t full_vec_ct = sample_ct / kInt16PerVec; // If all bytes are equal to 0 or numer_mask, all calls in this // block must be hardcall/missing. const VecUc vec0 = vecuc_setzero(); @@ -5615,7 +10720,7 @@ break; } } - sample_idx = vec_idx * (kBytesPerVec / 2); + sample_idx = vec_idx * kInt16PerVec; } else if (bit_precision == 16) { // 2x2 bytes per entry const uint32_t full_vec_ct = sample_ct / (kBytesPerVec / 4); diff -Nru plink2-2.00~a3-200116+dfsg/plink2_import.h plink2-2.00~a3-200217+dfsg/plink2_import.h --- plink2-2.00~a3-200116+dfsg/plink2_import.h 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_import.h 2020-02-01 03:44:03.000000000 +0000 @@ -93,6 +93,8 @@ PglErr VcfToPgen(const char* vcfname, const char* preexisting_psamname, const char* const_fid, const char* dosage_import_field, MiscFlags misc_flags, ImportFlags import_flags, uint32_t no_samples_ok, uint32_t hard_call_thresh, uint32_t dosage_erase_thresh, double import_dosage_certainty, char id_delim, char idspace_to, int32_t vcf_min_gq, int32_t vcf_min_dp, int32_t vcf_max_dp, VcfHalfCall halfcall_mode, FamCol fam_cols, uint32_t max_thread_ct, char* outname, char* outname_end, ChrInfo* cip, uint32_t* pgen_generated_ptr, uint32_t* psam_generated_ptr); +PglErr BcfToPgen(const char* bcfname, const char* preexisting_psamname, const char* const_fid, const char* dosage_import_field, MiscFlags misc_flags, ImportFlags import_flags, uint32_t no_samples_ok, uint32_t hard_call_thresh, uint32_t dosage_erase_thresh, double import_dosage_certainty, char id_delim, char idspace_to, int32_t vcf_min_gq, int32_t vcf_min_dp, int32_t vcf_max_dp, VcfHalfCall halfcall_mode, FamCol fam_cols, uint32_t max_thread_ct, char* outname, char* outname_end, ChrInfo* cip, uint32_t* pgen_generated_ptr, uint32_t* psam_generated_ptr); + PglErr OxGenToPgen(const char* genname, const char* samplename, const char* ox_single_chr_str, const char* ox_missing_code, MiscFlags misc_flags, ImportFlags import_flags, OxfordImportFlags oxford_import_flags, uint32_t hard_call_thresh, uint32_t dosage_erase_thresh, double import_dosage_certainty, uint32_t max_thread_ct, char* outname, char* outname_end, ChrInfo* cip); PglErr OxBgenToPgen(const char* bgenname, const char* samplename, const char* const_fid, const char* ox_single_chr_str, const char* ox_missing_code, MiscFlags misc_flags, ImportFlags import_flags, OxfordImportFlags oxford_import_flags, uint32_t hard_call_thresh, uint32_t dosage_erase_thresh, double import_dosage_certainty, char id_delim, char idspace_to, uint32_t max_thread_ct, char* outname, char* outname_end, ChrInfo* cip); diff -Nru plink2-2.00~a3-200116+dfsg/plink2_matrix_calc.cc plink2-2.00~a3-200217+dfsg/plink2_matrix_calc.cc --- plink2-2.00~a3-200116+dfsg/plink2_matrix_calc.cc 2020-01-17 04:01:24.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_matrix_calc.cc 2020-02-03 04:37:13.000000000 +0000 @@ -3994,10 +3994,23 @@ ctx.thread_start = thread_start; double* grm; if (unlikely( - SetThreadCt(calc_thread_ct, &tg) || - bigstack_calloc_d((row_end_idx - row_start_idx) * row_end_idx, &grm))) { + SetThreadCt(calc_thread_ct, &tg))) { goto CalcGrm_ret_NOMEM; } + if (unlikely( + bigstack_calloc_d((row_end_idx - row_start_idx) * row_end_idx, &grm))) { + if (!grm_ptr) { + logerrputs("Error: Out of memory. If you are SURE you are performing the right matrix\ncomputation, you can split it into smaller pieces with --parallel, and then\nconcatenate the results. But before you try this, make sure the program you're\nproviding the matrix to can actually handle such a large input file.\n"); + } else { + // Need to edit this if there are ever non-PCA ways to get here. + if (!(grm_flags & (kfGrmMatrixShapemask | kfGrmListmask | kfGrmBin))) { + logerrputs("Error: Out of memory. Consider \"--pca approx\" instead.\n"); + } else { + logerrputs("Error: Out of memory. Consider \"--pca approx\" (and not writing the GRM to\ndisk) instead.\n"); + } + } + goto CalcGrm_ret_NOMEM_CUSTOM; + } ctx.sample_ct = row_end_idx; ctx.grm = grm; uint32_t* sample_include_cumulative_popcounts; @@ -4499,6 +4512,9 @@ CalcGrm_ret_NOMEM: reterr = kPglRetNomem; break; + CalcGrm_ret_NOMEM_CUSTOM: + reterr = kPglRetNomemCustomMsg; + break; CalcGrm_ret_OPEN_FAIL: reterr = kPglRetOpenFail; break; @@ -7355,13 +7371,9 @@ snprintf(g_logbuf, kLogbufSize, "Error: Variant-score name in column %" PRIuPTR " of %s is too long.\n", vscore_idx + id_col_ct + 1, in_fname); goto Vscore_ret_MALFORMED_INPUT_WW; } - if (unlikely(S_CAST(uintptr_t, tmp_alloc_end - tmp_alloc_base) <= cur_slen)) { + if (StoreStringAtEnd(tmp_alloc_base, name_iter, cur_slen, &tmp_alloc_end, &(vscore_names[vscore_idx]))) { goto Vscore_ret_NOMEM; } - tmp_alloc_end -= cur_slen + 1; - char* cur_name = R_CAST(char*, tmp_alloc_end); - vscore_names[vscore_idx] = cur_name; - memcpyx(cur_name, name_iter, cur_slen, '\0'); name_iter = name_end; } ++line_idx; @@ -7369,10 +7381,9 @@ } else { for (uintptr_t vscore_num = 1; vscore_num <= vscore_ct; ++vscore_num) { const uint32_t cur_blen = 7 + UintSlen(vscore_num); - if (unlikely(S_CAST(uintptr_t, tmp_alloc_end - tmp_alloc_base) < cur_blen)) { + if (PtrWSubCk(tmp_alloc_base, cur_blen, &tmp_alloc_end)) { goto Vscore_ret_NOMEM; } - tmp_alloc_end -= cur_blen; char* cur_name_iter = R_CAST(char*, tmp_alloc_end); vscore_names[vscore_num - 1] = cur_name_iter; cur_name_iter = strcpya_k(cur_name_iter, "VSCORE"); diff -Nru plink2-2.00~a3-200116+dfsg/plink2_misc.cc plink2-2.00~a3-200217+dfsg/plink2_misc.cc --- plink2-2.00~a3-200116+dfsg/plink2_misc.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_misc.cc 2020-02-09 04:38:37.000000000 +0000 @@ -535,10 +535,9 @@ } else { // reuse old storage if we can, allocate when we must if (old_slen1 < new_slen1) { - if (S_CAST(uintptr_t, tmp_alloc_end - tmp_alloc_base) <= new_slen1) { + if (PtrWSubCk(tmp_alloc_base, new_slen1 + 1, &tmp_alloc_end)) { goto UpdateVarAlleles_ret_NOMEM; } - tmp_alloc_end -= new_slen1 + 1; cur_alleles[old_allele1_match] = R_CAST(char*, tmp_alloc_end); } memcpyx(cur_alleles[old_allele1_match], new_allele1_start, new_slen1, '\0'); @@ -547,10 +546,9 @@ cur_alleles[old_allele2_match] = K_CAST(char*, &(g_one_char_strs[ctou32(new_allele2_start[0]) * 2])); } else { if (old_slen2 < new_slen2) { - if (S_CAST(uintptr_t, tmp_alloc_end - tmp_alloc_base) <= new_slen2) { + if (PtrWSubCk(tmp_alloc_base, new_slen2 + 1, &tmp_alloc_end)) { goto UpdateVarAlleles_ret_NOMEM; } - tmp_alloc_end -= new_slen2 + 1; cur_alleles[old_allele2_match] = R_CAST(char*, tmp_alloc_end); } memcpyx(cur_alleles[old_allele2_match], new_allele2_start, new_slen2, '\0'); @@ -1560,6 +1558,15 @@ TextStream txs; PreinitTextStream(&txs); { + reterr = ForceNonFifo(fname); + if (unlikely(reterr)) { + if (reterr == kPglRetOpenFail) { + logerrprintfww(kErrprintfFopen, "--update-ids file", strerror(errno)); + } else { + logerrprintfww(kErrprintfRewind, "--update-ids file"); + } + goto PrescanSampleIds_ret_1; + } reterr = InitTextStream(fname, kTextStreamBlenFast, 1, &txs); if (unlikely(reterr)) { goto PrescanSampleIds_ret_TSTREAM_FAIL; @@ -1673,6 +1680,7 @@ if (unlikely(TextStreamErrcode2(&txs, &reterr))) { goto PrescanSampleIds_ret_TSTREAM_FAIL; } + reterr = kPglRetSuccess; siip->max_sample_id_blen = max_sample_id_blen; if (new_sid_present) { siip->max_sid_blen = max_sid_blen; @@ -1704,6 +1712,15 @@ TextStream txs; PreinitTextStream(&txs); { + reterr = ForceNonFifo(fname); + if (unlikely(reterr)) { + if (reterr == kPglRetOpenFail) { + logerrprintfww(kErrprintfFopen, "--update-parents file", strerror(errno)); + } else { + logerrprintfww(kErrprintfRewind, "--update-parents file"); + } + goto PrescanParentalIds_ret_1; + } // permit very long lines since this can be pointed at .ped files // possible minor todo: could save longest line length for later reference reterr = SizeAndInitTextStream(fname, bigstack_left() - (bigstack_left() / 4), MAXV(max_thread_ct - 1, 1), &txs); @@ -1827,7 +1844,7 @@ // probable todo: deduplicate shared code with PrescanSampleIds reterr = InitTextStream(fname, kTextStreamBlenFast, 1, &txs); if (unlikely(reterr)) { - goto UpdateSampleIds_ret_TSTREAM_REWIND_FAIL; + goto UpdateSampleIds_ret_TSTREAM_FAIL; } const char* line_iter; uint32_t is_header_line; @@ -1837,7 +1854,7 @@ if (unlikely(!line_iter)) { reterr = TextStreamRawErrcode(&txs); // This function is no longer called when the original file is empty. - goto UpdateSampleIds_ret_TSTREAM_REWIND_FAIL; + goto UpdateSampleIds_ret_TSTREAM_FAIL; } is_header_line = (*line_iter == '#'); } while (is_header_line && (!tokequal_k(&(line_iter[1]), "OLD_FID")) && (!tokequal_k(&(line_iter[1]), "OLD_IID"))); @@ -1969,7 +1986,7 @@ } } if (unlikely(TextStreamErrcode2(&txs, &reterr))) { - goto UpdateSampleIds_ret_TSTREAM_REWIND_FAIL; + goto UpdateSampleIds_ret_TSTREAM_FAIL; } reterr = kPglRetSuccess; if (miss_ct) { @@ -1983,10 +2000,12 @@ UpdateSampleIds_ret_NOMEM: reterr = kPglRetNomem; break; + UpdateSampleIds_ret_TSTREAM_FAIL: + TextStreamErrPrint("--update-ids file", &txs); + break; UpdateSampleIds_ret_REWIND_FAIL: + logerrprintfww(kErrprintfRewind, "--update-ids file"); reterr = kPglRetRewindFail; - UpdateSampleIds_ret_TSTREAM_REWIND_FAIL: - TextStreamErrPrintRewind("--update-ids file", &txs, &reterr); break; UpdateSampleIds_ret_MALFORMED_INPUT: reterr = kPglRetMalformedInput; @@ -2012,7 +2031,7 @@ // into its own function. reterr = SizeAndInitTextStream(fname, bigstack_left() - (bigstack_left() / 4), MAXV(max_thread_ct - 1, 1), &txs); if (unlikely(reterr)) { - goto UpdateSampleParents_ret_TSTREAM_REWIND_FAIL; + goto UpdateSampleParents_ret_TSTREAM_FAIL; } const char* line_iter; uint32_t is_header_line; @@ -2021,7 +2040,7 @@ line_iter = TextGet(&txs); if (unlikely(!line_iter)) { reterr = TextStreamRawErrcode(&txs); - goto UpdateSampleParents_ret_TSTREAM_REWIND_FAIL; + goto UpdateSampleParents_ret_TSTREAM_FAIL; } is_header_line = (*line_iter == '#'); } while (is_header_line && (!tokequal_k(&(line_iter[1]), "FID")) && (!tokequal_k(&(line_iter[1]), "IID"))); @@ -2130,7 +2149,7 @@ } } if (unlikely(TextStreamErrcode2(&txs, &reterr))) { - goto UpdateSampleParents_ret_TSTREAM_REWIND_FAIL; + goto UpdateSampleParents_ret_REWIND_FAIL; } reterr = kPglRetSuccess; if (miss_ct) { @@ -2144,10 +2163,12 @@ UpdateSampleParents_ret_NOMEM: reterr = kPglRetNomem; break; + UpdateSampleParents_ret_TSTREAM_FAIL: + TextStreamErrPrint("--update-parents file", &txs); + break; UpdateSampleParents_ret_REWIND_FAIL: + logerrprintfww(kErrprintfRewind, "--update-parents file"); reterr = kPglRetRewindFail; - UpdateSampleParents_ret_TSTREAM_REWIND_FAIL: - TextStreamErrPrintRewind("--update-parents file", &txs, &reterr); break; UpdateSampleParents_ret_MALFORMED_INPUT: reterr = kPglRetMalformedInput; @@ -2215,7 +2236,7 @@ col_num = 3; } if (unlikely(id_col_ct >= col_num)) { - logerrputs("Error: --update-sex 'col-num=' parameter too small (it refers to a sample ID\ncolumn).\n"); + logerrputs("Error: --update-sex 'col-num=' argument too small (it refers to a sample ID\ncolumn).\n"); goto UpdateSampleSexes_ret_MALFORMED_INPUT; } postid_col_idx = col_num - id_col_ct; @@ -7942,7 +7963,7 @@ const uint32_t slen2 = strlen(sample_fmtid2); if (unlikely(slen1 + slen2 + 2 > fname_extrachar_limit)) { logputs("\n"); - logerrputs("Error: Sample ID and/or --out parameter too long for --sample-diff pairwise\nmode.\n"); + logerrputs("Error: Sample ID and/or --out argument too long for --sample-diff pairwise\nmode.\n"); goto SdiffMainBatch_ret_INCONSISTENT_INPUT; } char* fname_iter = &(outname_end[1]); @@ -7988,7 +8009,7 @@ const uint32_t base_slen = strlen(sample_fmtid); if (unlikely(base_slen >= fname_extrachar_limit)) { logputs("\n"); - logerrputs("Error: Sample ID and/or --out parameter too long for --sample-diff base= mode.\n"); + logerrputs("Error: Sample ID and/or --out argument too long for --sample-diff base= mode.\n"); goto SdiffMainBatch_ret_INCONSISTENT_INPUT; } *fname_iter++ = '.'; diff -Nru plink2-2.00~a3-200116+dfsg/plink2_pvar.cc plink2-2.00~a3-200217+dfsg/plink2_pvar.cc --- plink2-2.00~a3-200116+dfsg/plink2_pvar.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_pvar.cc 2020-02-09 04:35:20.000000000 +0000 @@ -287,16 +287,14 @@ // er, do we really want to make a separate allocation here? see if we can // remove this. (that would impose more constraints on downstream // variant-ID-changing functions, though.) - *tmp_alloc_endp -= overflow_substitute_blen; - if (unlikely((*tmp_alloc_endp) < tmp_alloc_base)) { + if (PtrWSubCk(tmp_alloc_base, overflow_substitute_blen, tmp_alloc_endp)) { return 1; } memcpy(*tmp_alloc_endp, vtp->missing_id_match, overflow_substitute_blen); id_slen = 0; cur_overflow = 1; } else { - *tmp_alloc_endp -= id_slen + 1; - if (unlikely((*tmp_alloc_endp) < tmp_alloc_base)) { + if (PtrWSubCk(tmp_alloc_base, id_slen + 1, tmp_alloc_endp)) { return 1; } char* id_iter = R_CAST(char*, *tmp_alloc_endp); @@ -1589,12 +1587,9 @@ if (filter_slen > max_filter_slen) { max_filter_slen = filter_slen; } - tmp_alloc_end -= filter_slen + 1; - if (unlikely(tmp_alloc_end < tmp_alloc_base)) { + if (StoreStringAtEnd(tmp_alloc_base, filter_token, filter_slen, &tmp_alloc_end, &(cur_filter_storage[variant_idx_lowbits]))) { goto LoadPvar_ret_NOMEM; } - cur_filter_storage[variant_idx_lowbits] = R_CAST(char*, tmp_alloc_end); - memcpyx(tmp_alloc_end, filter_token, filter_slen, '\0'); } } if (load_filter_col > 1) { @@ -1615,8 +1610,7 @@ uint32_t id_slen; if ((!varid_templatep) || (missing_varid_match_slen && ((token_slens[1] != missing_varid_match_slen) || (!memequal(token_ptrs[1], missing_varid_match, missing_varid_match_slen))))) { id_slen = token_slens[1]; - tmp_alloc_end -= id_slen + 1; - if (unlikely(tmp_alloc_end < tmp_alloc_base)) { + if (PtrWSubCk(tmp_alloc_base, id_slen + 1, &tmp_alloc_end)) { goto LoadPvar_ret_NOMEM; } memcpyx(tmp_alloc_end, token_ptrs[1], id_slen, '\0'); @@ -1650,12 +1644,9 @@ } *allele_storage_iter = &(g_one_char_strs[2 * ctou32(geno_char)]); } else { - tmp_alloc_end -= ref_slen + 1; - if (unlikely(tmp_alloc_end < tmp_alloc_base)) { + if (StoreStringAtEndK(tmp_alloc_base, ref_allele, ref_slen, &tmp_alloc_end, allele_storage_iter)) { goto LoadPvar_ret_NOMEM; } - memcpyx(tmp_alloc_end, ref_allele, ref_slen, '\0'); - *allele_storage_iter = R_CAST(char*, tmp_alloc_end); if (ref_slen > max_allele_slen) { max_allele_slen = ref_slen; } @@ -1681,11 +1672,9 @@ if (unlikely(!cur_allele_slen)) { goto LoadPvar_ret_EMPTY_ALLELE_CODE; } - if (PtrWSubCk(tmp_alloc_base, cur_allele_slen + 1, &tmp_alloc_end)) { + if (StoreStringAtEndK(tmp_alloc_base, linebuf_iter, cur_allele_slen, &tmp_alloc_end, allele_storage_iter)) { goto LoadPvar_ret_NOMEM; } - memcpyx(tmp_alloc_end, linebuf_iter, cur_allele_slen, '\0'); - *allele_storage_iter = R_CAST(char*, tmp_alloc_end); if (cur_allele_slen > max_allele_slen) { max_allele_slen = cur_allele_slen; } @@ -1705,11 +1694,9 @@ } *allele_storage_iter = &(g_one_char_strs[2 * ctou32(geno_char)]); } else { - if (PtrWSubCk(tmp_alloc_base, remaining_alt_char_ct + 1, &tmp_alloc_end)) { + if (StoreStringAtEndK(tmp_alloc_base, linebuf_iter, remaining_alt_char_ct, &tmp_alloc_end, allele_storage_iter)) { goto LoadPvar_ret_NOMEM; } - memcpyx(tmp_alloc_end, linebuf_iter, remaining_alt_char_ct, '\0'); - *allele_storage_iter = R_CAST(char*, tmp_alloc_end); if (remaining_alt_char_ct > max_allele_slen) { max_allele_slen = remaining_alt_char_ct; } @@ -1833,9 +1820,9 @@ *raw_variant_ct_ptr = raw_variant_ct; uintptr_t allele_idx_end = allele_storage_iter - allele_storage; BigstackFinalizeCp(allele_storage, allele_idx_end); - // We may clobber this object soon, so close it now. - if (unlikely(CleanupTextStream(&pvar_txs, &reterr))) { - logerrprintfww(kErrprintfFread, pvarname, strerror(errno)); + // We may clobber this object soon, so close it now (verifying + // rewindability first, if necessary). + if (unlikely(CleanupTextStream2(pvarname, &pvar_txs, &reterr))) { goto LoadPvar_ret_1; } uintptr_t* allele_idx_offsets = nullptr; @@ -2058,7 +2045,18 @@ *vpos_sortstatus_ptr = vpos_sortstatus; *allele_storage_ptr = allele_storage; // if only INFO:PR flag present, no need to reload - *info_reload_slen_ptr = (info_nonpr_present || info_pr_nonflag_present)? info_reload_slen : 0; + if (!(info_nonpr_present || info_pr_nonflag_present)) { + info_reload_slen = 0; + } + if (info_reload_slen) { + // treat open-fail as rewind-fail here + if (unlikely(ForceNonFifo(pvarname))) { + logerrprintfww(kErrprintfRewind, pvarname); + reterr = kPglRetRewindFail; + goto LoadPvar_ret_1; + } + } + *info_reload_slen_ptr = info_reload_slen; } while (0) { diff -Nru plink2-2.00~a3-200116+dfsg/plink2_set.cc plink2-2.00~a3-200217+dfsg/plink2_set.cc --- plink2-2.00~a3-200116+dfsg/plink2_set.cc 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_set.cc 2020-01-24 03:58:28.000000000 +0000 @@ -59,7 +59,14 @@ const uint32_t chr_name_slen = first_token_end - line_start; *first_token_end = '\0'; const uint32_t cur_chr_code = GetChrCode(line_start, cip, chr_name_slen); - if (unlikely(IsI32Neg(cur_chr_code))) { + if (IsI32Neg(cur_chr_code)) { + // kludge (21 Jan 2020): --extract/exclude range should not error out + // if a line mentions a chromosome code not in the dataset. + // TODO: better condition for skipping this line. (not totally + // trivial since some future callers may need to track empty sets) + if (likely((!set_ct_ptr) && (cur_chr_code == UINT32_MAX))) { + continue; + } snprintf(g_logbuf, kLogbufSize, "Error: Invalid chromosome code on line %" PRIuPTR " of %s.\n", line_idx, file_descrip); goto LoadIntervalBed_ret_MALFORMED_INPUT_WW; } @@ -193,7 +200,10 @@ const uint32_t chr_name_slen = first_token_end - line_start; *first_token_end = '\0'; const uint32_t cur_chr_code = GetChrCode(line_start, cip, chr_name_slen); - if (unlikely(IsI32Neg(cur_chr_code))) { + if (IsI32Neg(cur_chr_code)) { + if (likely((!set_ct_ptr) && (cur_chr_code == UINT32_MAX))) { + continue; + } snprintf(g_logbuf, kLogbufSize, "Error: Invalid chromosome code on line %" PRIuPTR " of %s.\n", line_idx, file_descrip); goto LoadIntervalBed_ret_MALFORMED_INPUT_WW; } @@ -332,7 +342,7 @@ return reterr; } -PglErr ExtractExcludeRange(const char* fnames, const ChrInfo* cip, const uint32_t* variant_bps, uint32_t raw_variant_ct, VfilterType vft, uint32_t zero_based, uint32_t max_thread_ct, uintptr_t* variant_include, uint32_t* variant_ct_ptr) { +PglErr ExtractExcludeRange(const char* fnames, const ChrInfo* cip, const uint32_t* variant_bps, uint32_t raw_variant_ct, VfilterType vft, uint32_t zero_based, uint32_t bed_border_bp, uint32_t max_thread_ct, uintptr_t* variant_include, uint32_t* variant_ct_ptr) { const uint32_t orig_variant_ct = *variant_ct_ptr; if (!orig_variant_ct) { return kPglRetSuccess; @@ -367,7 +377,7 @@ fname_txs = fnames_iter; } MakeSetRange** range_arr = nullptr; - reterr = LoadIntervalBed(cip, variant_bps, nullptr, fname_txs, zero_based, 0, 0, 0, 0, 0, 0, &txs, nullptr, nullptr, nullptr, nullptr, &range_arr); + reterr = LoadIntervalBed(cip, variant_bps, nullptr, fname_txs, zero_based, 0, bed_border_bp, 0, 0, 0, 0, &txs, nullptr, nullptr, nullptr, nullptr, &range_arr); if (unlikely(reterr)) { goto ExtractExcludeRange_ret_1; } diff -Nru plink2-2.00~a3-200116+dfsg/plink2_set.h plink2-2.00~a3-200217+dfsg/plink2_set.h --- plink2-2.00~a3-200116+dfsg/plink2_set.h 2020-01-03 23:31:15.000000000 +0000 +++ plink2-2.00~a3-200217+dfsg/plink2_set.h 2020-01-24 03:58:28.000000000 +0000 @@ -24,7 +24,7 @@ namespace plink2 { #endif -PglErr ExtractExcludeRange(const char* fname, const ChrInfo* cip, const uint32_t* variant_bps, uint32_t raw_variant_ct, VfilterType vft, uint32_t ibed0, uint32_t max_thread_ct, uintptr_t* variant_include, uint32_t* variant_ct_ptr); +PglErr ExtractExcludeRange(const char* fnames, const ChrInfo* cip, const uint32_t* variant_bps, uint32_t raw_variant_ct, VfilterType vft, uint32_t zero_based, uint32_t bed_border_bp, uint32_t max_thread_ct, uintptr_t* variant_include, uint32_t* variant_ct_ptr); #ifdef __cplusplus }