        -:    0:Source:LikeR.c
        -:    0:Graph:./LikeR.gcno
        -:    0:Data:./LikeR.gcda
        -:    0:Runs:3
        -:    1:/*
        -:    2: * This file was generated automatically by ExtUtils::ParseXS version 3.57 from the
        -:    3: * contents of LikeR.xs. Do not edit this file, edit LikeR.xs instead.
        -:    4: *
        -:    5: *    ANY CHANGES MADE HERE WILL BE LOST!
        -:    6: *
        -:    7: */
        -:    8:
        -:    9:#line 1 "LikeR.xs"
        -:   10:/* --- C HELPER SECTION --- */
        -:   11:#define PERL_NO_GET_CONTEXT
        -:   12:#include "EXTERN.h"
        -:   13:#include "perl.h"
        -:   14:#include "XSUB.h"
        -:   15:#include "ppport.h"
        -:   16:#include <math.h>
        -:   17:#include <ctype.h>
        -:   18:#include <stdlib.h>
        -:   19:#include <float.h>
        -:   20:#include <string.h>
        -:   21:/* Ensure Perl's PRNG is seeded, matching the lazy-evaluation of Perl's rand() */
        -:   22:#define AUTO_SEED_PRNG() \
        -:   23:    do { \
        -:   24:        if (!PL_srand_called) { \
        -:   25:            (void)seedDrand01((Rand_seed_t)Perl_seed(aTHX)); \
        -:   26:            PL_srand_called = TRUE; \
        -:   27:        } \
        -:   28:    } while (0)
        -:   29:
        -:   30:// ---------------------------------------
        -:   31://   Helpers for Random Number Generation
        -:   32:// ---------------------------------------
        -:   33:#ifndef M_PI
        -:   34:#define M_PI 3.14159265358979323846
        -:   35:#endif
        -:   36:// C helper for EXACT Non-central T-distribution CDF via Numerical Integration.
        -:   37:// This perfectly replicates R's pt(..., ncp) exactness without requiring complex Beta functions.
        -:   38:static double exact_pnt(double t, double df, double ncp) {
        -:   39:	if (df <= 0.0) return 0.0;
        -:   40:	
        -:   41:	unsigned short int n_steps = 30000;
        -:   42:	double step = 1.0 / n_steps;
        -:   43:	double integral = 0.0;
        -:   44:	double half_df = df / 2.0;
        -:   45:
        -:   46:	double log_coef = log(2.0) + half_df * log(half_df) - lgamma(half_df);
        -:   47:	double root_half = 0.70710678118654752440; // 1 / sqrt(2)
        -:   48:
        -:   49:	for (unsigned short i = 1; i < n_steps; i++) {
        -:   50:		double u = i * step;
        -:   51:		double w = u / (1.0 - u);
        -:   52:		
        -:   53:		// Scaled Chi-distribution log-density
        -:   54:		double log_M = log_coef + (df - 1.0) * log(w) - half_df * w * w;
        -:   55:		double M = exp(log_M);
        -:   56:		
        -:   57:		// Exact Normal CDF using the C standard library's erfc function
        -:   58:		double z = t * w - ncp;
        -:   59:		double pnorm_val = 0.5 * erfc(-z * root_half);
        -:   60:		
        -:   61:		double weight = (i % 2 != 0) ? 4.0 : 2.0;
        -:   62:		integral += weight * (pnorm_val * M / ((1.0 - u) * (1.0 - u)));
        -:   63:	}
        -:   64:	
        -:   65:	return integral * (step / 3.0);
        -:   66:}
        -:   67:// --- Math Helpers for P-values and Confidence Intervals --- 
        -:   68:
        -:   69:// Ranking helper with tie adjustment (matches R's tie handling)
        -:   70:typedef struct { double val; size_t idx; double rank; } RankInfo;
        -:   71:static int compare_rank(const void *restrict a, const void *restrict b) {
        -:   72:	double diff = ((RankInfo*)a)->val - ((RankInfo*)b)->val;
        -:   73:	return (diff > 0) - (diff < 0);
        -:   74:}
        -:   75:
        -:   76:static int compare_index(const void *restrict a, const void *restrict b) {
        -:   77:	return ((RankInfo*)a)->idx - ((RankInfo*)b)->idx;
        -:   78:}
        -:   79:
        -:   80:static void compute_ranks(double *restrict data, double *restrict ranks, size_t n) {
        -:   81:	RankInfo *restrict items = safemalloc(n * sizeof(RankInfo));
        -:   82:	for (size_t i = 0; i < n; i++) {
        -:   83:		items[i].val = data[i];
        -:   84:		items[i].idx = i;
        -:   85:	}
        -:   86:	qsort(items, n, sizeof(RankInfo), compare_rank);
        -:   87:	// Handle ties by averaging ranks
        -:   88:	for (size_t i = 0; i < n; ) {
        -:   89:		size_t j = i + 1;
        -:   90:		while (j < n && items[j].val == items[i].val) j++;
        -:   91:		double avg_rank = (i + 1 + j) / 2.0;
        -:   92:		for (size_t k = i; k < j; k++) items[k].rank = avg_rank;
        -:   93:		i = j;
        -:   94:	}
        -:   95:	qsort(items, n, sizeof(RankInfo), compare_index);
        -:   96:	for (size_t i = 0; i < n; i++) ranks[i] = items[i].rank;
        -:   97:	Safefree(items);
        -:   98:}
        -:   99:// Generates a single binomial random variate. 
        -:  100://Uses the standard Bernoulli trial loop. Drand01() taps into Perl's PRNG.
        -:  101:static size_t generate_binomial(const size_t size, const double prob) {
        -:  102:	if (prob <= 0.0) return 0;
        -:  103:	if (prob >= 1.0) return size;
        -:  104:
        -:  105:	size_t successes = 0;
        -:  106:	for (size_t i = 0; i < size; i++) {
        -:  107:		if (Drand01() <= prob) successes++;
        -:  108:	}
        -:  109:	return successes;
        -:  110:}
        -:  111:// Helper: log combination
        -:  112:static double log_choose(size_t n, size_t k) {
        -:  113:	return lgamma((double)n + 1.0) - lgamma((double)k + 1.0) - lgamma((double)(n - k) + 1.0);
        -:  114:}
        -:  115:
        -:  116:// Log-space tails for non-central hypergeometric
        -:  117:static void calc_tails_logspace(size_t a, size_t min_x, size_t max_x, double omega, const double *logdc, double *restrict lower_tail, double *restrict upper_tail) {
        -:  118:	double max_d = -1e300, log_omega = log(omega);
        -:  119:
        -:  120:	for(size_t k = 0; k <= max_x - min_x; ++k) {
        -:  121:	  double d_val = logdc[k] + log_omega * (min_x + k);
        -:  122:	  if (d_val > max_d) max_d = d_val;
        -:  123:	}
        -:  124:
        -:  125:	double sum_d = 0.0;
        -:  126:	for(size_t k = 0; k <= max_x - min_x; ++k) {
        -:  127:	  sum_d += exp(logdc[k] + log_omega * (min_x + k) - max_d);
        -:  128:	}
        -:  129:
        -:  130:	*lower_tail = 0.0;
        -:  131:	*upper_tail = 0.0;
        -:  132:
        -:  133:	for(size_t k = 0; k <= max_x - min_x; ++k) {
        -:  134:	  double p_prob = exp(logdc[k] + log_omega * (min_x + k) - max_d) / sum_d;
        -:  135:	  if (min_x + k <= a) *lower_tail += p_prob;
        -:  136:	  if (min_x + k >= a) *upper_tail += p_prob;
        -:  137:	}
        -:  138:}
        -:  139:
        -:  140:// Exact stats using log-space
        -:  141:static void calculate_exact_stats(size_t a, size_t b, size_t c, size_t d, double conf_level, const char*restrict alt, double *restrict mle_or, double *restrict ci_low, double *restrict ci_high) {
        -:  142:    double alpha = 1.0 - conf_level;
        -:  143:    size_t r1 = a + b, r2 = c + d, c1 = a + c;
        -:  144:    size_t min_x = (r2 > c1) ? 0 : c1 - r2;
        -:  145:    size_t max_x = (r1 < c1) ? r1 : c1;
        -:  146:    
        -:  147:    bool is_less = (strcmp(alt, "less") == 0);
        -:  148:    bool is_greater = (strcmp(alt, "greater") == 0);
        -:  149:
        -:  150:    double *restrict logdc = (double*)safemalloc((max_x - min_x + 1) * sizeof(double));
        -:  151:    double denom = log_choose(r1 + r2, c1);
        -:  152:    for(size_t x = min_x; x <= max_x; ++x) {
        -:  153:        logdc[x - min_x] = log_choose(r1, x) + log_choose(r2, c1 - x) - denom;
        -:  154:    }
        -:  155:
        -:  156:    // MLE
        -:  157:    if (a == min_x && a == max_x) *mle_or = 1.0;
        -:  158:    else if (a == min_x) *mle_or = 0.0;
        -:  159:    else if (a == max_x) *mle_or = INFINITY;
        -:  160:    else {
        -:  161:        double log_low = -100.0, log_high = 100.0;
        -:  162:        for (unsigned short int i = 0; i < 3000; i++) {
        -:  163:            double log_mid = 0.5 * (log_low + log_high);
        -:  164:            double max_d = -1e300;
        -:  165:            for(size_t k = 0; k <= max_x - min_x; ++k) {
        -:  166:                double d_val = logdc[k] + log_mid * (min_x + k);
        -:  167:                if (d_val > max_d) max_d = d_val;
        -:  168:            }
        -:  169:            double sum_d = 0.0, exp_val = 0.0;
        -:  170:            for(size_t k = 0; k <= max_x - min_x; ++k) {
        -:  171:                double p_prob = exp(logdc[k] + log_mid * (min_x + k) - max_d);
        -:  172:                sum_d += p_prob;
        -:  173:                exp_val += (min_x + k) * p_prob;
        -:  174:            }
        -:  175:            exp_val /= sum_d;
        -:  176:            
        -:  177:            if (exp_val > a) log_high = log_mid;
        -:  178:            else log_low = log_mid;
        -:  179:            if (log_high - log_low < 1e-15) break;
        -:  180:        }
        -:  181:        *mle_or = exp(0.5 * (log_low + log_high));
        -:  182:    }
        -:  183:
        -:  184:    *ci_low = 0.0;
        -:  185:    *ci_high = INFINITY;
        -:  186:
        -:  187:    // Lower CI
        -:  188:    if (!is_less) { 
        -:  189:        double target_alpha = is_greater ? alpha : alpha / 2.0;
        -:  190:        if (a != min_x) {
        -:  191:            double log_low = -100.0, log_high = 100.0, best = 1.0, best_err = 1e9, lt, ut;
        -:  192:            for (unsigned short int i = 0; i < 1000; i++) {
        -:  193:                double log_mid = 0.5 * (log_low + log_high);
        -:  194:                double mid = exp(log_mid);
        -:  195:                calc_tails_logspace(a, min_x, max_x, mid, logdc, &lt, &ut);
        -:  196:                double err = fabs(ut - target_alpha);
        -:  197:                if (err < best_err) { best_err = err; best = mid; }
        -:  198:                if (ut > target_alpha) log_high = log_mid;
        -:  199:                else log_low = log_mid;
        -:  200:                if (log_high - log_low < 1e-15) break;
        -:  201:            }
        -:  202:            *ci_low = best;
        -:  203:        }
        -:  204:    }
        -:  205:
        -:  206:    // Upper CI
        -:  207:    if (!is_greater) { 
        -:  208:        double target_alpha = is_less ? alpha : alpha / 2.0;
        -:  209:        if (a != max_x) {
        -:  210:            double log_low = -100.0, log_high = 100.0, best = 1.0, best_err = 1e9, lt, ut;
        -:  211:            for (unsigned short int i = 0; i < 1000; i++) {
        -:  212:                double log_mid = 0.5 * (log_low + log_high);
        -:  213:                double mid = exp(log_mid);
        -:  214:                calc_tails_logspace(a, min_x, max_x, mid, logdc, &lt, &ut);
        -:  215:                double err = fabs(lt - target_alpha);
        -:  216:                if (err < best_err) { best_err = err; best = mid; }
        -:  217:                if (lt > target_alpha) log_low = log_mid;
        -:  218:                else log_high = log_mid;
        -:  219:                if (log_high - log_low < 1e-15) break;
        -:  220:            }
        -:  221:            *ci_high = best;
        -:  222:        }
        -:  223:    }
        -:  224:    safefree(logdc);
        -:  225:}
        -:  226:
        -:  227:// Exact p-value using log-space
        -:  228:static double exact_p_value(size_t a, size_t b, size_t c, size_t d, const char* alt) {
        -:  229:    size_t r1 = a + b, r2 = c + d, c1 = a + c;
        -:  230:    size_t min_x = (r2 > c1) ? 0 : c1 - r2;
        -:  231:    size_t max_x = (r1 < c1) ? r1 : c1;
        -:  232:
        -:  233:    double *logdc = (double*)safemalloc((max_x - min_x + 1) * sizeof(double));
        -:  234:    double denom = log_choose(r1 + r2, c1);
        -:  235:    for(size_t x = min_x; x <= max_x; ++x) {
        -:  236:        logdc[x - min_x] = log_choose(r1, x) + log_choose(r2, c1 - x) - denom;
        -:  237:    }
        -:  238:
        -:  239:    double p_val = 0.0;
        -:  240:    
        -:  241:    if (strcmp(alt, "less") == 0) {
        -:  242:        for(size_t x = min_x; x <= a; ++x) p_val += exp(logdc[x - min_x]);
        -:  243:    } else if (strcmp(alt, "greater") == 0) {
        -:  244:        for(size_t x = a; x <= max_x; ++x) p_val += exp(logdc[x - min_x]);
        -:  245:    } else {
        -:  246:        double p_obs = exp(logdc[a - min_x]);
        -:  247:        double relErr = 1.0 + 1e-7;
        -:  248:        for(size_t x = min_x; x <= max_x; ++x) {
        -:  249:            double p_cur = exp(logdc[x - min_x]);
        -:  250:            if (p_cur <= p_obs * relErr) p_val += p_cur;
        -:  251:        }
        -:  252:    }
        -:  253:    
        -:  254:    safefree(logdc);
        -:  255:    return (p_val > 1.0) ? 1.0 : p_val;
        -:  256:}
        -:  257:/* -----------------------------------------------------------------------
        -:  258: * Helpers for lm Linear Regression: OLS Matrix Math & Formula Parsing
        -:  259: * ----------------------------------------------------------------------- */
        -:  260:
        -:  261:/* Sweep operator for symmetric positive-definite matrices (e.g., XtX).
        -:  262: * This gracefully handles collinearity by bypassing aliased columns.
        -:  263: * Utilizes a relative tolerance check to prevent dropping micro-variance features.
        -:  264: */
        -:  265:static int sweep_matrix_ols(double *restrict A, size_t n, bool *restrict aliased) {
        -:  266:	int rank = 0;
        -:  267:	double *restrict orig_diag = (double*)safemalloc(n * sizeof(double));
        -:  268:
        -:  269:	// Save the original diagonal values to use as a baseline for relative variance
        -:  270:	for (size_t k = 0; k < n; k++) {
        -:  271:		aliased[k] = false;
        -:  272:		orig_diag[k] = A[k * n + k];
        -:  273:	}
        -:  274:
        -:  275:	for (size_t k = 0; k < n; k++) {
        -:  276:		// Check pivot for collinearity using a RELATIVE tolerance
        -:  277:		// (Fallback to a tiny absolute tolerance of 1e-24 to catch literal zero vectors)
        -:  278:		if (fabs(A[k * n + k]) <= 1e-10 * orig_diag[k] || fabs(A[k * n + k]) < 1e-24) {
        -:  279:			aliased[k] = true;
        -:  280:			// Isolate this column so it doesn't affect the rest of the matrix
        -:  281:			for (size_t i = 0; i < n; i++) { 
        -:  282:				A[k * n + i] = 0.0; 
        -:  283:				A[i * n + k] = 0.0; 
        -:  284:			}
        -:  285:			continue;
        -:  286:		}
        -:  287:		rank++;
        -:  288:		double pivot = 1.0 / A[k * n + k];
        -:  289:		A[k * n + k] = 1.0;
        -:  290:		for (size_t j = 0; j < n; j++) A[k * n + j] *= pivot;
        -:  291:		for (size_t i = 0; i < n; i++) {
        -:  292:			if (i != k && A[i * n + k] != 0.0) {
        -:  293:				  double factor = A[i * n + k];
        -:  294:				  A[i * n + k] = 0.0;
        -:  295:				  for (size_t j = 0; j < n; j++) {
        -:  296:				       A[i * n + j] -= factor * A[k * n + j];
        -:  297:				  }
        -:  298:			}
        -:  299:		}
        -:  300:	}
        -:  301:	Safefree(orig_diag);
        -:  302:	return rank;
        -:  303:}
        -:  304:
        -:  305:// Internal extractor resolving single data values. Returns NAN on missing or non-numeric.
        -:  306:static double get_data_value(HV *restrict data_hoa, HV **restrict row_hashes, unsigned int i, const char *restrict var) {
        -:  307:    SV **restrict val = NULL;
        -:  308:    if (row_hashes) {
        -:  309:        val = hv_fetch(row_hashes[i], var, strlen(var), 0);
        -:  310:        if (val && SvROK(*val) && SvTYPE(SvRV(*val)) == SVt_PVAV) {
        -:  311:            AV*restrict av = (AV*)SvRV(*val);
        -:  312:            val = av_fetch(av, 0, 0);
        -:  313:        }
        -:  314:    } else if (data_hoa) {
        -:  315:        SV**restrict col = hv_fetch(data_hoa, var, strlen(var), 0);
        -:  316:        if (col && SvROK(*col) && SvTYPE(SvRV(*col)) == SVt_PVAV) {
        -:  317:            AV*restrict av = (AV*)SvRV(*col);
        -:  318:            val = av_fetch(av, i, 0);
        -:  319:        }
        -:  320:    }
        -:  321:    if (val && SvOK(*val)) {
        -:  322:        if (looks_like_number(*val)) return SvNV(*val);
        -:  323:        return NAN; // Catch strings like "blue"
        -:  324:    }
        -:  325:    return NAN; // Catch undef/missing keys
        -:  326:}
        -:  327:
        -:  328:// Helper: Get all available columns for the '.' operator expansion
        -:  329:static AV* get_all_columns(HV *restrict data_hoa, HV **restrict row_hashes, size_t n) {
        -:  330:    AV *cols = newAV();
        -:  331:    if (data_hoa) {
        -:  332:        hv_iterinit(data_hoa);
        -:  333:        HE *entry;
        -:  334:        while ((entry = hv_iternext(data_hoa))) {
        -:  335:            av_push(cols, newSVsv(hv_iterkeysv(entry)));
        -:  336:        }
        -:  337:    } else if (row_hashes && n > 0 && row_hashes[0]) {
        -:  338:        hv_iterinit(row_hashes[0]);
        -:  339:        HE *entry;
        -:  340:        while ((entry = hv_iternext(row_hashes[0]))) {
        -:  341:            av_push(cols, newSVsv(hv_iterkeysv(entry)));
        -:  342:        }
        -:  343:    }
        -:  344:    return cols;
        -:  345:}
        -:  346:
        -:  347:// Recursive formula resolver with tightened NaN and Null handling
        -:  348:static double evaluate_term(HV *restrict data_hoa, HV **restrict row_hashes, unsigned int i, const char *restrict term) {
        -:  349:    if (!term || term[0] == '\0') return NAN;
        -:  350:
        -:  351:    char *restrict term_cpy = savepv(term); 
        -:  352:    char *restrict colon = strchr(term_cpy, ':');
        -:  353:    if (colon) {
        -:  354:        *colon = '\0';
        -:  355:        double left = evaluate_term(data_hoa, row_hashes, i, term_cpy);
        -:  356:        double right = evaluate_term(data_hoa, row_hashes, i, colon + 1);
        -:  357:        Safefree(term_cpy); 
        -:  358:        
        -:  359:        if (isnan(left) || isnan(right)) return NAN;
        -:  360:        return left * right;
        -:  361:    }
        -:  362:    if (strncmp(term_cpy, "I(", 2) == 0) {
        -:  363:        char *restrict end = strrchr(term_cpy, ')');
        -:  364:        if (end) *end = '\0';
        -:  365:        char *restrict inner = term_cpy + 2;
        -:  366:        char *restrict caret = strchr(inner, '^');
        -:  367:        int power = 1;
        -:  368:        if (caret) {
        -:  369:            *caret = '\0';
        -:  370:            power = atoi(caret + 1);
        -:  371:        }
        -:  372:        double v = get_data_value(data_hoa, row_hashes, i, inner);
        -:  373:        Safefree(term_cpy); 
        -:  374:        
        -:  375:        if (isnan(v)) return NAN;
        -:  376:        return power == 1 ? v : pow(v, power);
        -:  377:    }
        -:  378:    double result = get_data_value(data_hoa, row_hashes, i, term_cpy);
        -:  379:    Safefree(term_cpy); 
        -:  380:    return result;
        -:  381:}
        -:  382:
        -:  383:// Helper to infer column type from its first valid element
        -:  384:static bool is_column_categorical(HV *restrict data_hoa, HV **restrict row_hashes, size_t n, const char *restrict var) {
        -:  385:	for (size_t i = 0; i < n; i++) {
        -:  386:		SV **restrict val = NULL;
        -:  387:		if (row_hashes) {
        -:  388:			val = hv_fetch(row_hashes[i], var, strlen(var), 0);
        -:  389:			if (val && SvROK(*val) && SvTYPE(SvRV(*val)) == SVt_PVAV) {
        -:  390:				 AV*restrict av = (AV*)SvRV(*val);
        -:  391:				 val = av_fetch(av, 0, 0);
        -:  392:			}
        -:  393:		} else if (data_hoa) {
        -:  394:			SV **restrict col = hv_fetch(data_hoa, var, strlen(var), 0);
        -:  395:			if (col && SvROK(*col) && SvTYPE(SvRV(*col)) == SVt_PVAV) {
        -:  396:				 AV*restrict av = (AV*)SvRV(*col);
        -:  397:				 val = av_fetch(av, i, 0);
        -:  398:			}
        -:  399:		}
        -:  400:		if (val && SvOK(*val)) {
        -:  401:			if (looks_like_number(*val)) return false; // First valid is number -> Numeric Column
        -:  402:			return true; // First valid is string -> Categorical Column
        -:  403:		}
        -:  404:	}
        -:  405:	return false;
        -:  406:}
        -:  407:
        -:  408:/* Internal extractor resolving single data string values using dynamic allocation. */
        -:  409:static char* get_data_string_alloc(HV *restrict data_hoa, HV **restrict row_hashes, size_t i, const char *restrict var) {
        -:  410:	SV **restrict val = NULL;
        -:  411:	if (row_hashes) {
        -:  412:		val = hv_fetch(row_hashes[i], var, strlen(var), 0);
        -:  413:		if (val && SvROK(*val) && SvTYPE(SvRV(*val)) == SVt_PVAV) {
        -:  414:			AV*restrict av = (AV*)SvRV(*val);
        -:  415:			val = av_fetch(av, 0, 0);
        -:  416:		}
        -:  417:	} else if (data_hoa) {
        -:  418:		SV **restrict col = hv_fetch(data_hoa, var, strlen(var), 0);
        -:  419:		if (col && SvROK(*col) && SvTYPE(SvRV(*col)) == SVt_PVAV) {
        -:  420:			AV*restrict av = (AV*)SvRV(*col);
        -:  421:			val = av_fetch(av, i, 0);
        -:  422:		}
        -:  423:	}
        -:  424:	if (val && SvOK(*val)) {
        -:  425:	  return savepv(SvPV_nolen(*val)); /* Allocates and returns string */
        -:  426:	}
        -:  427:	return NULL;
        -:  428:}
        -:  429:
        -:  430:// Struct for sorting p-values while remembering their original index
        -:  431:typedef struct {
        -:  432:	double p;
        -:  433:	size_t orig_idx;
        -:  434:} PVal;
        -:  435:
        -:  436:// Comparator for qsort
        -:  437:static int cmp_pval(const void *restrict a, const void *restrict b) {
        -:  438:	double diff = ((PVal*)a)->p - ((PVal*)b)->p;
        -:  439:	if (diff < 0) return -1;
        -:  440:	if (diff > 0) return 1;
        -:  441:	/* Stabilize sort by falling back to original index */
        -:  442:	return ((PVal*)a)->orig_idx - ((PVal*)b)->orig_idx; 
        -:  443:}
        -:  444:/* -----------------------------------------------------------------------
        -:  445: * Helpers for cor(): ranking (Spearman), Pearson r, Kendall tau-b
        -:  446: * ----------------------------------------------------------------------- */
        -:  447:/* Item used to sort values while remembering their original index,
        -:  448: * needed for average-rank tie-breaking in Spearman correlation.        */
        -:  449:typedef struct {
        -:  450:	double val;
        -:  451:	size_t idx;
        -:  452:} RankItem;
        -:  453:
        -:  454:static int cmp_rank_item(const void *restrict a, const void *restrict b) {
        -:  455:	double diff = ((RankItem*)a)->val - ((RankItem*)b)->val;
        -:  456:	if (diff < 0) return -1;
        -:  457:	if (diff > 0) return  1;
        -:  458:	return 0;
        -:  459:}
        -:  460:
        -:  461:/* Compute 1-based average ranks with tie-breaking into out[].
        -:  462: * in[] is not modified.                                                 */
        -:  463:static void rank_data(const double *restrict in, double *restrict out, size_t n) {
        -:  464:	RankItem *restrict ri;
        -:  465:	Newx(ri, n, RankItem);
        -:  466:	for (size_t i = 0; i < n; i++) { ri[i].val = in[i]; ri[i].idx = i; }
        -:  467:	qsort(ri, n, sizeof(RankItem), cmp_rank_item);
        -:  468:
        -:  469:	size_t i = 0;
        -:  470:	while (i < n) {
        -:  471:		size_t j = i;
        -:  472:		/* Find the full extent of this tie group */
        -:  473:		while (j + 1 < n && ri[j + 1].val == ri[j].val) j++;
        -:  474:		/* All members get the average of ranks i+1 … j+1 (1-based) */
        -:  475:		double avg = (double)(i + j) / 2.0 + 1.0;
        -:  476:		for (size_t k = i; k <= j; k++) out[ri[k].idx] = avg;
        -:  477:		i = j + 1;
        -:  478:	}
        -:  479:	Safefree(ri);
        -:  480:}
        -:  481:
        -:  482:/* Pearson product-moment r between two n-element arrays.
        -:  483: * Returns NAN when either variable has zero variance (matches R).       */
        -:  484:static double pearson_corr(const double *restrict x, const double *restrict y, size_t n) {
        -:  485:	double sx = 0, sy = 0, sxy = 0, sx2 = 0, sy2 = 0;
        -:  486:	for (size_t i = 0; i < n; i++) {
        -:  487:	  sx  += x[i];     sy  += y[i];
        -:  488:	  sxy += x[i]*y[i]; sx2 += x[i]*x[i]; sy2 += y[i]*y[i];
        -:  489:	}
        -:  490:	double num = (double)n * sxy - sx * sy;
        -:  491:	double den = sqrt(((double)n * sx2 - sx*sx) * ((double)n * sy2 - sy*sy));
        -:  492:	if (den == 0.0) return NAN;
        -:  493:	return num / den;
        -:  494:}
        -:  495:
        -:  496:/* Kendall's tau-b between two n-element arrays.
        -:  497: *
        -:  498: *   tau-b = (C − D) / sqrt((C + D + T_x)(C + D + T_y))
        -:  499: *
        -:  500: * where C = concordant pairs, D = discordant, T_x = pairs tied only on
        -:  501: * x, T_y = pairs tied only on y.  Joint ties (both zero) are excluded
        -:  502: * from numerator and denominator, matching R's cor(method="kendall").
        -:  503: * Returns NAN when the denominator is zero.                             */
        -:  504:static double kendall_tau_b(const double *restrict x, const double *restrict y, unsigned int n) {
        -:  505:	size_t C = 0, D = 0, tie_x = 0, tie_y = 0;
        -:  506:	for (size_t i = 0; i < n - 1; i++) {
        -:  507:	  for (size_t j = i + 1; j < n; j++) {
        -:  508:		   int sx = (x[i] > x[j]) - (x[i] < x[j]);   /* sign of x[i]-x[j] */
        -:  509:		   int sy = (y[i] > y[j]) - (y[i] < y[j]);
        -:  510:		   if      (sx == 0 && sy == 0) { /* joint tie — not counted */ }
        -:  511:		   else if (sx == 0)            tie_x++;
        -:  512:		   else if (sy == 0)            tie_y++;
        -:  513:		   else if (sx == sy)           C++;
        -:  514:		   else                         D++;
        -:  515:	  }
        -:  516:	}
        -:  517:	double denom = sqrt((double)(C + D + tie_x) * (double)(C + D + tie_y));
        -:  518:	if (denom == 0.0) return NAN;
        -:  519:	return (double)(C - D) / denom;
        -:  520:}
        -:  521:
        -:  522:/* Single dispatch: compute correlation according to method string.
        -:  523: * Allocates and frees temporary rank arrays internally for Spearman.   */
        -:  524:static double compute_cor(const double *restrict x, const double *restrict y,
        -:  525:                           size_t n, const char *restrict method) {
        -:  526:	if (strcmp(method, "spearman") == 0) {
        -:  527:	  double *restrict rx, *restrict ry;
        -:  528:	  Newx(rx, n, double); Newx(ry, n, double);
        -:  529:	  rank_data(x, rx, n);
        -:  530:	  rank_data(y, ry, n);
        -:  531:	  double r = pearson_corr(rx, ry, n);
        -:  532:	  Safefree(rx); Safefree(ry);
        -:  533:	  return r;
        -:  534:	}
        -:  535:	if (strcmp(method, "kendall") == 0)
        -:  536:	  return kendall_tau_b(x, y, n);
        -:  537:	/* default: pearson */
        -:  538:	return pearson_corr(x, y, n);
        -:  539:}
        -:  540:
        -:  541:// Math macros
        -:  542:#define MAX_ITER 500
        -:  543:#define EPS 3.0e-15
        -:  544:#define FPMIN 1.0e-30
        -:  545:
        -:  546:static double _incbeta_cf(double a, double b, double x) {
        -:  547:	int m;
        -:  548:	double aa, c, d, del, h, qab, qam, qap;
        -:  549:	qab = a + b; qap = a + 1.0; qam = a - 1.0;
        -:  550:	c = 1.0; d = 1.0 - qab * x / qap;
        -:  551:	if (fabs(d) < FPMIN) d = FPMIN;
        -:  552:	d = 1.0 / d; h = d;
        -:  553:	for (m = 1; m <= MAX_ITER; m++) {
        -:  554:	  int m2 = 2 * m;
        -:  555:	  aa = m * (b - m) * x / ((qam + m2) * (a + m2));
        -:  556:	  d = 1.0 + aa * d;
        -:  557:	  if (fabs(d) < FPMIN) d = FPMIN;
        -:  558:	  c = 1.0 + aa / c;
        -:  559:	  if (fabs(c) < FPMIN) c = FPMIN;
        -:  560:	  d = 1.0 / d; h *= d * c;
        -:  561:	  aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
        -:  562:	  d = 1.0 + aa * d;
        -:  563:	  if (fabs(d) < FPMIN) d = FPMIN;
        -:  564:	  c = 1.0 + aa / c;
        -:  565:	  if (fabs(c) < FPMIN) c = FPMIN;
        -:  566:	  d = 1.0 / d; del = d * c; h *= del;
        -:  567:	  if (fabs(del - 1.0) < EPS) break;
        -:  568:	}
        -:  569:	return h;
        -:  570:}
        -:  571:
        -:  572:static double incbeta(double a, double b, double x) {
        -:  573:	if (x <= 0.0) return 0.0;
        -:  574:	if (x >= 1.0) return 1.0;
        -:  575:	double bt = exp(lgamma(a + b) - lgamma(a) - lgamma(b) + a * log(x) + b * log(1.0 - x));
        -:  576:	if (x < (a + 1.0) / (a + b + 2.0)) return bt * _incbeta_cf(a, b, x) / a;
        -:  577:	return 1.0 - bt * _incbeta_cf(b, a, 1.0 - x) / b;
        -:  578:}
        -:  579:
        -:  580:static double get_t_pvalue(double t, double df, const char*restrict alt) {
        -:  581:	double x = df / (df + t * t);
        -:  582:	double prob_2tail = incbeta(df / 2.0, 0.5, x);
        -:  583:	if (strcmp(alt, "less") == 0) return (t < 0) ? 0.5 * prob_2tail : 1.0 - 0.5 * prob_2tail;
        -:  584:	if (strcmp(alt, "greater") == 0) return (t > 0) ? 0.5 * prob_2tail : 1.0 - 0.5 * prob_2tail;
        -:  585:	return prob_2tail;
        -:  586:}
        -:  587:
        -:  588:// Bisection algorithm to find the inverse t-distribution (Critical t-value)
        -:  589:static double qt_tail(double df, double p_tail) {
        -:  590:	double low = 0.0, high = 1.0;
        -:  591:	// Find upper bound
        -:  592:	while (get_t_pvalue(high, df, "greater") > p_tail) {
        -:  593:	  low = high;
        -:  594:	  high *= 2.0;
        -:  595:	  if (high > 1000000.0) break; /* Fallback limit */
        -:  596:	}
        -:  597:	// Bisect to find the root
        -:  598:	for (unsigned short int i = 0; i < 100; i++) {
        -:  599:	  double mid = (low + high) / 2.0;
        -:  600:	  double p_mid = get_t_pvalue(mid, df, "greater");
        -:  601:	  if (p_mid > p_tail) {
        -:  602:		   low = mid;
        -:  603:	  } else {
        -:  604:		   high = mid;
        -:  605:	  }
        -:  606:	  if (high - low < 1e-8) break;
        -:  607:	}
        -:  608:	return (low + high) / 2.0;
        -:  609:}
        -:  610:
        -:  611:int compare_doubles(const void *restrict a, const void *restrict b) {
        -:  612:	double da = *(const double*restrict)a;
        -:  613:	double db = *(const double*restrict)b;
        -:  614:	return (da > db) - (da < db);
        -:  615:}
        -:  616:/* Helper to calculate the number of bins using Sturges' formula: log2(n) + 1 */
        -:  617:static size_t calculate_sturges_bins(size_t n) {
        -:  618:	if (n == 0) return 1;
        -:  619:	return (size_t)(log((double)n) / log(2.0) + 1.0);
        -:  620:}
        -:  621:
        -:  622:// Logic for distributing data into bins (Optimized to O(N))
        -:  623:static void compute_hist_logic(double *restrict x, size_t n, double *restrict breaks, size_t n_bins, 
        -:  624:                               size_t *restrict counts, double *restrict mids, double *restrict density) {
        -:  625:	double total_n = (double)n;
        -:  626:	double min_val = breaks[0];
        -:  627:	double step = (n_bins > 0) ? (breaks[1] - breaks[0]) : 0.0;
        -:  628:	// Initialize counts and compute midpoints
        -:  629:	for (size_t i = 0; i < n_bins; i++) {
        -:  630:	  counts[i] = 0;
        -:  631:	  mids[i] = (breaks[i] + breaks[i+1]) / 2.0;
        -:  632:	}
        -:  633:	// Single O(N) pass to assign elements to bins
        -:  634:	if (step > 0.0) {
        -:  635:	  for (size_t j = 0; j < n; j++) {
        -:  636:		   double val = x[j];
        -:  637:		   // Ignore out-of-bounds or invalid values
        -:  638:		   if (isnan(val) || isinf(val) || val < min_val) continue;
        -:  639:		   // Calculate initial bin index mathematically
        -:  640:		   size_t idx = (size_t)((val - min_val) / step);
        -:  641:		   // Clamp to valid array bounds first to prevent overflow */
        -:  642:		   if (idx >= n_bins) {
        -:  643:		       idx = n_bins - 1;
        -:  644:		   }
        -:  645:		   /* Adjust for exact boundaries (R's right-inclusive default: (a, b]) */
        -:  646:		   /* If value is exactly on or slightly below the lower boundary of the assigned bin, 
        -:  647:		      it belongs in the previous bin. (First bin [a, b] is inclusive on both ends) */
        -:  648:		   while (idx > 0 && val <= breaks[idx]) {
        -:  649:		       idx--;
        -:  650:		   }
        -:  651:		   // Conversely, if floating-point truncation placed it too low, push it up
        -:  652:		   while (idx < n_bins - 1 && val > breaks[idx + 1]) {
        -:  653:		       idx++;
        -:  654:		   }
        -:  655:		   counts[idx]++;
        -:  656:	  }
        -:  657:	} else if (n_bins > 0) {
        -:  658:	  // Edge case: All data points have the exact same value (step == 0)
        -:  659:	  counts[0] = n;
        -:  660:	}
        -:  661:	// Compute densities
        -:  662:	for (size_t i = 0; i < n_bins; i++) {
        -:  663:	  double bin_width = breaks[i+1] - breaks[i];
        -:  664:	  if (bin_width > 0) {
        -:  665:		   density[i] = (double)counts[i] / (total_n * bin_width);
        -:  666:	  } else {
        -:  667:		   density[i] = (n_bins == 1) ? 1.0 : 0.0;
        -:  668:	  }
        -:  669:	}
        -:  670:}
        -:  671:
        -:  672:// Standard Normal CDF approximation
        -:  673:double approx_pnorm(double x) {
        -:  674:	return 0.5 * erfc(-x * 0.70710678118654752440); // 0.707... = 1/sqrt(2)
        -:  675:}
        -:  676:#ifndef M_SQRT1_2
        -:  677:#define M_SQRT1_2 0.70710678118654752440
        -:  678:#endif
        -:  679:
        -:  680:/* Macro for exact Wilcoxon 3D array indexing */
        -:  681:#define DP_INDEX(i, j, k, n2, max_u) ((i) * ((n2) + 1) * ((max_u) + 1) + (j) * ((max_u) + 1) + (k))
        -:  682:static double inverse_normal_cdf(double p) {
        -:  683:	double a[4] = {2.50662823884, -18.61500062529, 41.39119773534, -25.44106049637};
        -:  684:	double b[4] = {-8.47351093090, 23.08336743743, -21.06224101826, 3.13082909833};
        -:  685:	double c[9] = {0.3374754822726147, 0.9761690190917186, 0.1607979714918209,
        -:  686:		          0.0276438810333863, 0.0038405729373609, 0.0003951896511919,
        -:  687:		          0.0000321767881768, 0.0000002888167364, 0.0000003960315187};
        -:  688:	double x, r, y;
        -:  689:	y = p - 0.5;
        -:  690:	if (fabs(y) < 0.42) {
        -:  691:	  r = y * y;
        -:  692:	  x = y * (((a[3]*r + a[2])*r + a[1])*r + a[0]) /
        -:  693:		       ((((b[3]*r + b[2])*r + b[1])*r + b[0])*r + 1.0);
        -:  694:	} else {
        -:  695:	  r = p;
        -:  696:	  if (y > 0) r = 1.0 - p;
        -:  697:	  r = log(-log(r));
        -:  698:	  x = c[0] + r * (c[1] + r * (c[2] + r * (c[3] + r * (c[4] +
        -:  699:		   r * (c[5] + r * (c[6] + r * (c[7] + r * c[8])))))));
        -:  700:	  if (y < 0) x = -x;
        -:  701:	}
        -:  702:	return x;
        -:  703:}
        -:  704:/* -----------------------------------------------------------------------
        -:  705: * Exact Spearman p-value via exhaustive permutation enumeration.
        -:  706: *
        -:  707: * Under H0, all n! orderings of ranks are equally probable.  We visit
        -:  708: * every permutation of {1..n} with Heap's algorithm (O(n!), no allocs
        -:  709: * inside the loop) and count how many yield S ≤ s_obs ("lower tail",
        -:  710: * i.e. rho ≥ rho_obs) and how many yield S ≥ s_obs ("upper tail").
        -:  711: *
        -:  712: * Mirrors R's default: exact = (n < 10) with no ties.
        -:  713: * Valid up to n = 9 (362 880 iterations — negligible cost).
        -:  714: * ----------------------------------------------------------------------- */
        -:  715:static double spearman_exact_pvalue(double s_obs, size_t n, const char *restrict alt) {
        -:  716:	int *restrict perm = (int*)safemalloc(n * sizeof(int));
        -:  717:	int *restrict c    = (int*)safemalloc(n * sizeof(int));
        -:  718:	for (size_t i = 0; i < n; i++) { perm[i] = i + 1; c[i] = 0; }
        -:  719:
        -:  720:	long count_le = 0, count_ge = 0, total = 0;
        -:  721:
        -:  722:	#define TALLY_PERM() do {                                    \
        -:  723:	  double s_ = 0.0;                                     \
        -:  724:	  for (int ii = 0; ii < n; ii++) {                    \
        -:  725:		   double d_ = (double)(ii + 1) - (double)perm[ii];\
        -:  726:		   s_ += d_ * d_;                                   \
        -:  727:	  }                                                    \
        -:  728:	  if (s_ <= s_obs + 1e-9) count_le++;                 \
        -:  729:	  if (s_ >= s_obs - 1e-9) count_ge++;                 \
        -:  730:	  total++;                                             \
        -:  731:	} while (0)
        -:  732:
        -:  733:	TALLY_PERM();   /* initial permutation [1, 2, ..., n] */
        -:  734:
        -:  735:	unsigned int k = 1;
        -:  736:	while (k < n) {
        -:  737:	  if (c[k] < k) {
        -:  738:		   int tmp;
        -:  739:		   if (k % 2 == 0) {
        -:  740:		       tmp = perm[0]; perm[0] = perm[k]; perm[k] = tmp;
        -:  741:		   } else {
        -:  742:		       tmp = perm[c[k]]; perm[c[k]] = perm[k]; perm[k] = tmp;
        -:  743:		   }
        -:  744:		   TALLY_PERM();
        -:  745:		   c[k]++;
        -:  746:		   k = 1;
        -:  747:	  } else {
        -:  748:		   c[k] = 0;
        -:  749:		   k++;
        -:  750:	  }
        -:  751:	}
        -:  752:	#undef TALLY_PERM
        -:  753:
        -:  754:	Safefree(perm); Safefree(c);
        -:  755:	/* p_le = P(S ≤ s_obs) ≡ P(rho ≥ rho_obs)  — upper rho tail
        -:  756:	* p_ge = P(S ≥ s_obs) ≡ P(rho ≤ rho_obs)  — lower rho tail  */
        -:  757:	double p_le = (double)count_le / (double)total;
        -:  758:	double p_ge = (double)count_ge / (double)total;
        -:  759:
        -:  760:	if (strcmp(alt, "greater") == 0) return p_le;
        -:  761:	if (strcmp(alt, "less")    == 0) return p_ge;
        -:  762:	/* two.sided: 2 × the smaller tail, clamped to 1 */
        -:  763:	double p = 2.0 * (p_le < p_ge ? p_le : p_ge);
        -:  764:	return (p > 1.0) ? 1.0 : p;
        -:  765:}
        -:  766:/* -----------------------------------------------------------------------
        -:  767: * Exact Kendall p-value via Mahonian Numbers (Inversions distribution)
        -:  768: * Matches R's behavior for N < 50 without ties.
        -:  769: * ----------------------------------------------------------------------- */
        -:  770:static double kendall_exact_pvalue(size_t n, double s_obs, const char *restrict alt) {
        -:  771:	long max_inv = (long)n * (n - 1) / 2;
        -:  772:	double *restrict dp = (double*)safemalloc((max_inv + 1) * sizeof(double));
        -:  773:	for (long i = 0; i <= max_inv; i++) dp[i] = 0.0;
        -:  774:	dp[0] = 1.0;
        -:  775:	/* Build the distribution of inversions via DP */
        -:  776:	for (size_t i = 2; i <= n; i++) {
        -:  777:	  double *restrict next_dp = (double*)safemalloc((max_inv + 1) * sizeof(double));
        -:  778:	  for (long k = 0; k <= max_inv; k++) next_dp[k] = 0.0;
        -:  779:	  int current_max_inv = i * (i - 1) / 2;
        -:  780:	  for (int k = 0; k <= current_max_inv; k++) {
        -:  781:		   double sum = 0;
        -:  782:		   for (int j = 0; j <= i - 1 && k - j >= 0; j++) {
        -:  783:		       sum += dp[k - j];
        -:  784:		   }
        -:  785:		   // Divide by 'i' directly to keep array as pure probabilities and prevent overflow
        -:  786:		   next_dp[k] = sum / (double)i;
        -:  787:	  }
        -:  788:	  Safefree(dp);
        -:  789:	  dp = next_dp;
        -:  790:	}
        -:  791:	// Convert S statistic to target number of inversions
        -:  792:	long i_obs = (long)round((max_inv - s_obs) / 2.0);
        -:  793:	if (i_obs < 0) i_obs = 0;
        -:  794:	if (i_obs > max_inv) i_obs = max_inv;
        -:  795:	double p_le = 0.0; /* P(S <= S_obs) */
        -:  796:	for (long k = i_obs; k <= max_inv; k++) p_le += dp[k];
        -:  797:	double p_ge = 0.0; /* P(S >= S_obs) */
        -:  798:	for (long k = 0; k <= i_obs; k++) p_ge += dp[k];
        -:  799:	Safefree(dp);
        -:  800:	if (strcmp(alt, "greater") == 0) return p_ge;
        -:  801:	if (strcmp(alt, "less") == 0) return p_le;
        -:  802:	// two.sided
        -:  803:	double p = 2.0 * (p_ge < p_le ? p_ge : p_le);
        -:  804:	return p > 1.0 ? 1.0 : p;
        -:  805:}
        -:  806:// F-distribution Cumulative Distribution Function P(F <= f)
        -:  807:static double pf(double f, double df1, double df2) {
        -:  808:	if (f <= 0.0) return 0.0;
        -:  809:	double x = (df1 * f) / (df1 * f + df2);
        -:  810:	return incbeta(df1 / 2.0, df2 / 2.0, x);
        -:  811:}
        -:  812:
        -:  813:/* Householder QR Decomposition for Sequential Sums of Squares */
        -:  814:/* Householder QR Decomposition for Sequential Sums of Squares */
        -:  815:static void apply_householder_aov(double** restrict X, double* restrict y, size_t n, size_t p, bool* restrict aliased, size_t* restrict rank_map) {
        -:  816:	size_t r = 0; // Rank/Row tracker
        -:  817:	for (size_t k = 0; k < p; k++) {
        -:  818:		aliased[k] = false;
        -:  819:		if (r >= n) {
        -:  820:			aliased[k] = true;
        -:  821:			continue;
        -:  822:		}
        -:  823:
        -:  824:		double max_val = 0;
        -:  825:		for (size_t i = r; i < n; i++) {
        -:  826:			if (fabs(X[i][k]) > max_val) max_val = fabs(X[i][k]);
        -:  827:		}
        -:  828:		if (max_val < 1e-10) { 
        -:  829:			aliased[k] = true; 
        -:  830:			continue; 
        -:  831:		} // Collinear or zero column
        -:  832:
        -:  833:		double norm = 0;
        -:  834:		for (size_t i = r; i < n; i++) {
        -:  835:			X[i][k] /= max_val;
        -:  836:			norm += X[i][k] * X[i][k];
        -:  837:		}
        -:  838:		norm = sqrt(norm);
        -:  839:		double s = (X[r][k] > 0) ? -norm : norm;
        -:  840:		double u1 = X[r][k] - s;
        -:  841:		X[r][k] = s * max_val;
        -:  842:
        -:  843:		for (size_t j = k + 1; j < p; j++) {
        -:  844:			double dot = u1 * X[r][j];
        -:  845:			for (size_t i = r + 1; i < n; i++) dot += X[i][j] * X[i][k];
        -:  846:			double tau = dot / (s * u1);
        -:  847:			X[r][j] += tau * u1;
        -:  848:			for (size_t i = r + 1; i < n; i++) X[i][j] += tau * X[i][k];
        -:  849:		}
        -:  850:
        -:  851:		// Transform the response vector y
        -:  852:		double dot_y = u1 * y[r];
        -:  853:		for (size_t i = r + 1; i < n; i++) dot_y += y[i] * X[i][k];
        -:  854:		double tau_y = dot_y / (s * u1);
        -:  855:		y[r] += tau_y * u1;
        -:  856:		for (size_t i = r + 1; i < n; i++) y[i] += tau_y * X[i][k];
        -:  857:
        -:  858:		rank_map[k] = r; // Map original column index to orthogonal row index
        -:  859:		r++;
        -:  860:	}
        -:  861:}
        -:  862:
        -:  863:// --- write_table Helpers ---
        -:  864:
        -:  865:// Sorts string arrays alphabetically
        -:  866:static int cmp_string_wt(const void *a, const void *b) {
        -:  867:	return strcmp(*(const char**)a, *(const char**)b);
        -:  868:}
        -:  869:
        -:  870:// Emulates Perl's /\D/ check
        -:  871:static bool contains_nondigit(SV *restrict sv) {
        -:  872:	if (!sv || !SvOK(sv)) return 0;
        -:  873:	STRLEN len;
        -:  874:	const char *restrict s = SvPVbyte(sv, len);
        -:  875:	for (size_t i = 0; i < len; i++) {
        -:  876:	  if (!isdigit(s[i])) return 1;
        -:  877:	}
        -:  878:	return 0;
        -:  879:}
        -:  880:
        -:  881:// Writes a properly quoted string dynamically
        -:  882:static void print_str_quoted(PerlIO *fh, const char *str, const char *sep) {
        -:  883:	if (!str) str = "";
        -:  884:	bool needs_quotes = 0;
        -:  885:	if (strstr(str, sep) != NULL || strchr(str, '"') != NULL || strchr(str, '\r') != NULL || strchr(str, '\n') != NULL) {
        -:  886:	  needs_quotes = 1;
        -:  887:	}
        -:  888:
        -:  889:	if (needs_quotes) {
        -:  890:	  PerlIO_putc(fh, '"');
        -:  891:	  for (const char *restrict p = str; *p; p++) {
        -:  892:		   if (*p == '"') {
        -:  893:		       PerlIO_putc(fh, '"');
        -:  894:		       PerlIO_putc(fh, '"');
        -:  895:		   } else {
        -:  896:		       PerlIO_putc(fh, *p);
        -:  897:		   }
        -:  898:	  }
        -:  899:	  PerlIO_putc(fh, '"');
        -:  900:	} else {
        -:  901:	  PerlIO_puts(fh, str);
        -:  902:	}
        -:  903:}
        -:  904:
        -:  905:// Writes an array of strings joined by sep
        -:  906:static void print_string_row(PerlIO *fh, const char **row, size_t len, const char *sep) {
        -:  907:	size_t sep_len = strlen(sep);
        -:  908:	for (size_t i = 0; i < len; i++) {
        -:  909:	  if (i > 0) PerlIO_write(fh, sep, sep_len);
        -:  910:	  if (row[i]) {
        -:  911:		   print_str_quoted(fh, row[i], sep);
        -:  912:	  } else {
        -:  913:		   print_str_quoted(fh, "", sep);
        -:  914:	  }
        -:  915:	}
        -:  916:	PerlIO_putc(fh, '\n');
        -:  917:}
        -:  918:// Calculates the Regularized Upper Incomplete Gamma Function Q(a, x)
        -:  919:// This perfectly replicates R's pchisq(..., lower.tail=FALSE)
        -:  920:double igamc(double a, double x) {
        -:  921:	if (x < 0.0 || a <= 0.0) return 1.0;
        -:  922:	if (x == 0.0) return 1.0;
        -:  923:
        -:  924:	// Series expansion for x < a + 1
        -:  925:	if (x < a + 1.0) {
        -:  926:		double sum = 1.0 / a;
        -:  927:		double term = 1.0 / a;
        -:  928:		double n = 1.0;
        -:  929:		while (fabs(term) > 1e-15) {
        -:  930:			term *= x / (a + n);
        -:  931:			sum += term;
        -:  932:			n += 1.0;
        -:  933:		}
        -:  934:		return 1.0 - (sum * exp(-x + a * log(x) - lgamma(a)));
        -:  935:	}
        -:  936:
        -:  937:	// Continued fraction for x >= a + 1
        -:  938:	double b = x + 1.0 - a;
        -:  939:	double c = 1.0 / 1e-30;
        -:  940:	double d = 1.0 / b;
        -:  941:	double h = d, i = 1.0;
        -:  942:	while (i < 10000) { // Safety bound
        -:  943:		double an = -i * (i - a);
        -:  944:		b += 2.0;
        -:  945:		d = an * d + b;
        -:  946:		if (fabs(d) < 1e-30) d = 1e-30;
        -:  947:		c = b + an / c;
        -:  948:		if (fabs(c) < 1e-30) c = 1e-30;
        -:  949:		d = 1.0 / d;
        -:  950:		double del = d * c;
        -:  951:		h *= del;
        -:  952:		if (fabs(del - 1.0) < 1e-15) break;
        -:  953:		i += 1.0;
        -:  954:	}
        -:  955:	return h * exp(-x + a * log(x) - lgamma(a));
        -:  956:}
        -:  957:
        -:  958:// Chi-Squared p-value is simply the Incomplete Gamma of (df/2, stat/2)
        -:  959:double get_p_value(double stat, int df) {
        -:  960:	if (df <= 0) return 1.0;
        -:  961:	if (stat <= 0.0) return 1.0;
        -:  962:	return igamc((double)df / 2.0, stat / 2.0);
        -:  963:}
        -:  964:
        -:  965:/* --- C HELPER SECTION --- */
        -:  966:#ifndef M_SQRT1_2
        -:  967:#define M_SQRT1_2 0.70710678118654752440
        -:  968:#endif
        -:  969:
        -:  970:/* Robust Binomial Coefficient using long double */
        -:  971:static long double choose_comb(int n, int k) {
        -:  972:	if (k < 0 || k > n) return 0.0L;
        -:  973:	if (k > n / 2) k = n - k;
        -:  974:	long double res = 1.0L;
        -:  975:	for (int i = 1; i <= k; i++) {
        -:  976:	  res = res * (long double)(n - i + 1) / (long double)i;
        -:  977:	}
        -:  978:	return res;
        -:  979:}
        -:  980:
        -:  981:/* Exact CDF for Mann-Whitney U: P(U <= q) 
        -:  982:   Mathematically identical to R's cwilcox generating function */
        -:  983:static double exact_pwilcox(double q, int m, int n) {
        -:  984:    int k = (int)floor(q + 1e-7); // R uses 1e-7 fuzz
        -:  985:    int max_u = m * n;
        -:  986:    if (k < 0) return 0.0;
        -:  987:    if (k >= max_u) return 1.0;
        -:  988:
        -:  989:    long double *restrict w = (long double *)safecalloc(max_u + 1, sizeof(long double));
        -:  990:    w[0] = 1.0L;
        -:  991:
        -:  992:    for (int j = 1; j <= n; j++) {
        -:  993:        for (int i = j; i <= max_u; i++) w[i] += w[i - j];
        -:  994:        for (int i = max_u; i >= j + m; i--) w[i] -= w[i - j - m];
        -:  995:    }
        -:  996:
        -:  997:    long double cum_p = 0.0L;
        -:  998:    for (int i = 0; i <= k; i++) cum_p += w[i];
        -:  999:    
        -: 1000:    long double total = choose_comb(m + n, n);
        -: 1001:    double result = (double)(cum_p / total);
        -: 1002:    
        -: 1003:    Safefree(w);
        -: 1004:    return result;
        -: 1005:}
        -: 1006:
        -: 1007:/* Exact CDF for Wilcoxon Signed Rank: P(V <= q) 
        -: 1008:   Mathematically identical to R's csignrank subset-sum DP */
        -: 1009:static double exact_psignrank(double q, int n) {
        -: 1010:	int k = (int)floor(q + 1e-7);
        -: 1011:	int max_v = n * (n + 1) / 2;
        -: 1012:	if (k < 0) return 0.0;
        -: 1013:	if (k >= max_v) return 1.0;
        -: 1014:
        -: 1015:	long double *restrict w = (long double *)safecalloc(max_v + 1, sizeof(long double));
        -: 1016:	w[0] = 1.0L;
        -: 1017:
        -: 1018:	for (int i = 1; i <= n; i++) {
        -: 1019:	  for (int j = max_v; j >= i; j--) w[j] += w[j - i];
        -: 1020:	}
        -: 1021:
        -: 1022:	long double cum_p = 0.0L;
        -: 1023:	for (int i = 0; i <= k; i++) cum_p += w[i];
        -: 1024:
        -: 1025:	long double total = powl(2.0L, (long double)n);
        -: 1026:	double result = (double)(cum_p / total);
        -: 1027:
        -: 1028:	Safefree(w);
        -: 1029:	return result;
        -: 1030:}
        -: 1031:
        -: 1032:static int cmp_rank_info(const void *a, const void *b) {
        -: 1033:	double da = ((const RankInfo*)a)->val;
        -: 1034:	double db = ((const RankInfo*)b)->val;
        -: 1035:	return (da > db) - (da < db);
        -: 1036:}
        -: 1037:
        -: 1038:static double rank_and_count_ties(RankInfo *restrict ri, size_t n, bool *restrict has_ties) {
        -: 1039:	if (n == 0) return 0.0;
        -: 1040:	qsort(ri, n, sizeof(RankInfo), cmp_rank_info);
        -: 1041:	size_t i = 0;
        -: 1042:	double tie_adj = 0.0;
        -: 1043:	*has_ties = 0;
        -: 1044:	while (i < n) {
        -: 1045:		size_t j = i + 1;
        -: 1046:		while (j < n && ri[j].val == ri[i].val) j++;
        -: 1047:		double r = (double)(i + 1 + j) / 2.0; 
        -: 1048:		for (size_t k = i; k < j; k++) ri[k].rank = r;
        -: 1049:		size_t t = j - i;
        -: 1050:		if (t > 1) { *has_ties = 1; tie_adj += ((double)t * t * t - t); }
        -: 1051:		i = j;
        -: 1052:	}
        -: 1053:	return tie_adj;
        -: 1054:}
        -: 1055:/* --- KS-TEST C HELPER SECTION --- */
        -: 1056:#ifndef M_PI_2
        -: 1057:#define M_PI_2 1.57079632679489661923
        -: 1058:#endif
        -: 1059:#ifndef M_PI_4
        -: 1060:#define M_PI_4 0.78539816339744830962
        -: 1061:#endif
        -: 1062:#ifndef M_1_SQRT_2PI
        -: 1063:#define M_1_SQRT_2PI 0.39894228040143267794
        -: 1064:#endif
        -: 1065:
        -: 1066:// Scalar integer power used by K2x
        -: 1067:static double r_pow_di(double x, int n) {
        -: 1068:	if (n == 0) return 1.0;
        -: 1069:	if (n < 0) return 1.0 / r_pow_di(x, -n);
        -: 1070:	double val = 1.0;
        -: 1071:	for (int i = 0; i < n; i++) val *= x;
        -: 1072:	return val;
        -: 1073:}
        -: 1074:
        -: 1075:// Two-sample two-sided asymptotic distribution
        -: 1076:static double K2l(double x, int lower, double tol) {
        -: 1077:	double s, z, p;
        -: 1078:	int k;
        -: 1079:	if(x <= 0.) {
        -: 1080:	  if(lower) p = 0.;
        -: 1081:	  else p = 1.;
        -: 1082:	} else if(x < 1.) {
        -: 1083:	  int k_max = (int) sqrt(2.0 - log(tol));
        -: 1084:	  double w = log(x);
        -: 1085:	  z = - (M_PI_2 * M_PI_4) / (x * x);
        -: 1086:	  s = 0;
        -: 1087:	  for(k = 1; k < k_max; k += 2) {
        -: 1088:		   s += exp(k * k * z - w);
        -: 1089:	  }
        -: 1090:	  p = s / M_1_SQRT_2PI;
        -: 1091:	  if(!lower) p = 1.0 - p;
        -: 1092:	} else {
        -: 1093:	  double new_val, old_val;
        -: 1094:	  z = -2.0 * x * x;
        -: 1095:	  s = -1.0;
        -: 1096:	  if(lower) {
        -: 1097:		   k = 1; old_val = 0.0; new_val = 1.0;
        -: 1098:	  } else {
        -: 1099:		   k = 2; old_val = 0.0; new_val = 2.0 * exp(z);
        -: 1100:	  }
        -: 1101:	  while(fabs(old_val - new_val) > tol) {
        -: 1102:		   old_val = new_val;
        -: 1103:		   new_val += 2.0 * s * exp(z * k * k);
        -: 1104:		   s *= -1.0;
        -: 1105:		   k++;
        -: 1106:	  }
        -: 1107:	  p = new_val;
        -: 1108:	}
        -: 1109:	return p;
        -: 1110:}
        -: 1111:
        -: 1112:// Auxiliary routines used by K2x() for matrix operations
        -: 1113:static void m_multiply(double *A, double *B, double *C, unsigned int m) {
        -: 1114:	for(unsigned int i = 0; i < m; i++) {
        -: 1115:	  for(unsigned int j = 0; j < m; j++) {
        -: 1116:		   double s = 0.;
        -: 1117:		   for(unsigned int k = 0; k < m; k++) s += A[i * m + k] * B[k * m + j];
        -: 1118:		   C[i * m + j] = s;
        -: 1119:	  }
        -: 1120:	}
        -: 1121:}
        -: 1122:
        -: 1123:static void m_power(double *A, int eA, double *V, int *eV, int m, int n) {
        -: 1124:    if(n == 1) {
        -: 1125:        for(int i = 0; i < m * m; i++) V[i] = A[i];
        -: 1126:        *eV = eA;
        -: 1127:        return;
        -: 1128:    }
        -: 1129:    m_power(A, eA, V, eV, m, n / 2);
        -: 1130:    double *restrict B = (double*) safecalloc(m * m, sizeof(double));
        -: 1131:    m_multiply(V, V, B, m);
        -: 1132:    int eB = 2 * (*eV);
        -: 1133:    if((n % 2) == 0) {
        -: 1134:        for(int i = 0; i < m * m; i++) V[i] = B[i];
        -: 1135:        *eV = eB;
        -: 1136:    } else {
        -: 1137:        m_multiply(A, B, V, m);
        -: 1138:        *eV = eA + eB;
        -: 1139:    }
        -: 1140:    if(V[(m / 2) * m + (m / 2)] > 1e140) {
        -: 1141:        for(int i = 0; i < m * m; i++) V[i] = V[i] * 1e-140;
        -: 1142:        *eV += 140;
        -: 1143:    }
        -: 1144:    Safefree(B);
        -: 1145:}
        -: 1146:
        -: 1147:// One-sample two-sided exact distribution
        -: 1148:static double K2x(int n, double d) {
        -: 1149:	int k = (int) (n * d) + 1;
        -: 1150:	int m = 2 * k - 1;
        -: 1151:	double h = k - n * d;
        -: 1152:	double *restrict H = (double*) safecalloc(m * m, sizeof(double));
        -: 1153:	double *restrict Q = (double*) safecalloc(m * m, sizeof(double));
        -: 1154:
        -: 1155:	for(int i = 0; i < m; i++) {
        -: 1156:	  for(int j = 0; j < m; j++) {
        -: 1157:		   if(i - j + 1 < 0) H[i * m + j] = 0;
        -: 1158:		   else H[i * m + j] = 1;
        -: 1159:	  }
        -: 1160:	}
        -: 1161:	for(int i = 0; i < m; i++) {
        -: 1162:	  H[i * m] -= r_pow_di(h, i + 1);
        -: 1163:	  H[(m - 1) * m + i] -= r_pow_di(h, (m - i));
        -: 1164:	}
        -: 1165:	H[(m - 1) * m] += ((2 * h - 1 > 0) ? r_pow_di(2 * h - 1, m) : 0);
        -: 1166:
        -: 1167:	for(int i = 0; i < m; i++) {
        -: 1168:	  for(int j = 0; j < m; j++) {
        -: 1169:		   if(i - j + 1 > 0) {
        -: 1170:		       for(int g = 1; g <= i - j + 1; g++) H[i * m + j] /= g;
        -: 1171:		   }
        -: 1172:	  }
        -: 1173:	}
        -: 1174:
        -: 1175:	int eH = 0, eQ;
        -: 1176:	m_power(H, eH, Q, &eQ, m, n);
        -: 1177:	double s = Q[(k - 1) * m + k - 1];
        -: 1178:
        -: 1179:	for(int i = 1; i <= n; i++) {
        -: 1180:	  s = s * (double)i / (double)n;
        -: 1181:	  if(s < 1e-140) {
        -: 1182:		   s *= 1e140;
        -: 1183:		   eQ -= 140;
        -: 1184:	  }
        -: 1185:	}
        -: 1186:	s *= pow(10.0, eQ);
        -: 1187:	Safefree(H);
        -: 1188:	Safefree(Q);
        -: 1189:	return s;
        -: 1190:}
        -: 1191:
        -: 1192:// Calculate D (two-sided), D+ (greater), and D- (less) simultaneously
        -: 1193:static void calc_2sample_stats(double *x, size_t nx, double *y, size_t ny,
        -: 1194:                               double *d, double *d_plus, double *d_minus) {
        -: 1195:	qsort(x, nx, sizeof(double), compare_doubles);
        -: 1196:	qsort(y, ny, sizeof(double), compare_doubles);
        -: 1197:	double max_d = 0.0, max_d_plus = 0.0, max_d_minus = 0.0;
        -: 1198:	size_t i = 0, j = 0;
        -: 1199:
        -: 1200:	while(i < nx || j < ny) {
        -: 1201:	  double val;
        -: 1202:	  if (i < nx && j < ny) val = (x[i] < y[j]) ? x[i] : y[j];
        -: 1203:	  else if (i < nx) val = x[i];
        -: 1204:	  else val = y[j];
        -: 1205:	  
        -: 1206:	  while(i < nx && x[i] <= val) i++;
        -: 1207:	  while(j < ny && y[j] <= val) j++;
        -: 1208:	  
        -: 1209:	  double cdf1 = (double)i / nx;
        -: 1210:	  double cdf2 = (double)j / ny;
        -: 1211:	  double diff = cdf1 - cdf2;
        -: 1212:	  
        -: 1213:	  if (diff > max_d_plus) max_d_plus = diff;
        -: 1214:	  if (-diff > max_d_minus) max_d_minus = -diff;
        -: 1215:	  if (fabs(diff) > max_d) max_d = fabs(diff);
        -: 1216:	}
        -: 1217:	*d = max_d;
        -: 1218:	*d_plus = max_d_plus;
        -: 1219:	*d_minus = max_d_minus;
        -: 1220:}
        -: 1221:
        -: 1222:// Branch the DP boundary check based on the 'alternative'
        -: 1223:static int psmirnov_exact_test(double q, double r, double s, int two_sided) {
        -: 1224:	if (two_sided) return (fabs(r - s) >= q);
        -: 1225:	return ((r - s) >= q); // Used for both D+ and D- via symmetry
        -: 1226:}
        -: 1227:
        -: 1228:// Evaluate the exact 2-sample probability
        -: 1229:static double psmirnov_exact_uniq_upper(double q, int m, int n, int two_sided) {
        -: 1230:	double md = (double) m, nd = (double) n;
        -: 1231:	double *u = (double *) safecalloc(n + 1, sizeof(double));
        -: 1232:	u[0] = 0.;
        -: 1233:
        -: 1234:	for(unsigned int j = 1; j <= n; j++) {
        -: 1235:	  if(psmirnov_exact_test(q, 0., j / nd, two_sided)) u[j] = 1.;
        -: 1236:	  else u[j] = u[j - 1];
        -: 1237:	}
        -: 1238:	for(unsigned int i = 1; i <= m; i++) {
        -: 1239:	  if(psmirnov_exact_test(q, i / md, 0., two_sided)) u[0] = 1.;
        -: 1240:	  for(int j = 1; j <= n; j++) {
        -: 1241:		   if(psmirnov_exact_test(q, i / md, j / nd, two_sided)) u[j] = 1.;
        -: 1242:		   else {
        -: 1243:		       double v = (double)(i) / (double)(i + j);
        -: 1244:		       double w = (double)(j) / (double)(i + j);
        -: 1245:		       u[j] = v * u[j] + w * u[j - 1];
        -: 1246:		   }
        -: 1247:	  }
        -: 1248:	}
        -: 1249:	double res = u[n];
        -: 1250:	Safefree(u);
        -: 1251:	return res;
        -: 1252:}
        -: 1253:
        -: 1254:static double p_body(double n, double delta, double sd, double sig_level, int tsample, int tside, bool strict) {
        -: 1255:	double nu = (n - 1.0) * (double)tsample;
        -: 1256:	if (nu < 1e-7) nu = 1e-7; 
        -: 1257:
        -: 1258:	// Ensure sig_level/tside is not truncated
        -: 1259:	double p_tail = sig_level / (double)tside;
        -: 1260:	double qu = qt_tail(nu, p_tail); // qt(p, df, lower.tail=FALSE)
        -: 1261:
        -: 1262:	double ncp = sqrt(n / (double)tsample) * (delta / sd);
        -: 1263:
        -: 1264:	if (strict && tside == 2) {
        -: 1265:	  // Use R-style tail calls: 1 - P(T < qu) + P(T < -qu)
        -: 1266:	  return (1.0 - exact_pnt(qu, nu, ncp)) + exact_pnt(-qu, nu, ncp);
        -: 1267:	} else {
        -: 1268:	  // Default: 1 - P(T < qu)
        -: 1269:	  // Ensure exact_pnt is using a convergence tolerance of at least 1e-15
        -: 1270:	  return 1.0 - exact_pnt(qu, nu, ncp);
        -: 1271:	}
        -: 1272:}
        -: 1273:// --- XS SECTION ---
        -: 1274:#line 1275 "LikeR.c"
        -: 1275:#ifndef PERL_UNUSED_VAR
        -: 1276:#  define PERL_UNUSED_VAR(var) if (0) var = var
        -: 1277:#endif
        -: 1278:
        -: 1279:#ifndef dVAR
        -: 1280:#  define dVAR		dNOOP
        -: 1281:#endif
        -: 1282:
        -: 1283:
        -: 1284:/* This stuff is not part of the API! You have been warned. */
        -: 1285:#ifndef PERL_VERSION_DECIMAL
        -: 1286:#  define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)
        -: 1287:#endif
        -: 1288:#ifndef PERL_DECIMAL_VERSION
        -: 1289:#  define PERL_DECIMAL_VERSION \
        -: 1290:	  PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)
        -: 1291:#endif
        -: 1292:#ifndef PERL_VERSION_GE
        -: 1293:#  define PERL_VERSION_GE(r,v,s) \
        -: 1294:	  (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))
        -: 1295:#endif
        -: 1296:#ifndef PERL_VERSION_LE
        -: 1297:#  define PERL_VERSION_LE(r,v,s) \
        -: 1298:	  (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s))
        -: 1299:#endif
        -: 1300:
        -: 1301:/* XS_INTERNAL is the explicit static-linkage variant of the default
        -: 1302: * XS macro.
        -: 1303: *
        -: 1304: * XS_EXTERNAL is the same as XS_INTERNAL except it does not include
        -: 1305: * "STATIC", ie. it exports XSUB symbols. You probably don't want that
        -: 1306: * for anything but the BOOT XSUB.
        -: 1307: *
        -: 1308: * See XSUB.h in core!
        -: 1309: */
        -: 1310:
        -: 1311:
        -: 1312:/* TODO: This might be compatible further back than 5.10.0. */
        -: 1313:#if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1)
        -: 1314:#  undef XS_EXTERNAL
        -: 1315:#  undef XS_INTERNAL
        -: 1316:#  if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING)
        -: 1317:#    define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name)
        -: 1318:#    define XS_INTERNAL(name) STATIC XSPROTO(name)
        -: 1319:#  endif
        -: 1320:#  if defined(__SYMBIAN32__)
        -: 1321:#    define XS_EXTERNAL(name) EXPORT_C XSPROTO(name)
        -: 1322:#    define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name)
        -: 1323:#  endif
        -: 1324:#  ifndef XS_EXTERNAL
        -: 1325:#    if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus)
        -: 1326:#      define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__)
        -: 1327:#      define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__)
        -: 1328:#    else
        -: 1329:#      ifdef __cplusplus
        -: 1330:#        define XS_EXTERNAL(name) extern "C" XSPROTO(name)
        -: 1331:#        define XS_INTERNAL(name) static XSPROTO(name)
        -: 1332:#      else
        -: 1333:#        define XS_EXTERNAL(name) XSPROTO(name)
        -: 1334:#        define XS_INTERNAL(name) STATIC XSPROTO(name)
        -: 1335:#      endif
        -: 1336:#    endif
        -: 1337:#  endif
        -: 1338:#endif
        -: 1339:
        -: 1340:/* perl >= 5.10.0 && perl <= 5.15.1 */
        -: 1341:
        -: 1342:
        -: 1343:/* The XS_EXTERNAL macro is used for functions that must not be static
        -: 1344: * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL
        -: 1345: * macro defined, the best we can do is assume XS is the same.
        -: 1346: * Dito for XS_INTERNAL.
        -: 1347: */
        -: 1348:#ifndef XS_EXTERNAL
        -: 1349:#  define XS_EXTERNAL(name) XS(name)
        -: 1350:#endif
        -: 1351:#ifndef XS_INTERNAL
        -: 1352:#  define XS_INTERNAL(name) XS(name)
        -: 1353:#endif
        -: 1354:
        -: 1355:/* Now, finally, after all this mess, we want an ExtUtils::ParseXS
        -: 1356: * internal macro that we're free to redefine for varying linkage due
        -: 1357: * to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use
        -: 1358: * XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to!
        -: 1359: */
        -: 1360:
        -: 1361:#undef XS_EUPXS
        -: 1362:#if defined(PERL_EUPXS_ALWAYS_EXPORT)
        -: 1363:#  define XS_EUPXS(name) XS_EXTERNAL(name)
        -: 1364:#else
        -: 1365:   /* default to internal */
        -: 1366:#  define XS_EUPXS(name) XS_INTERNAL(name)
        -: 1367:#endif
        -: 1368:
        -: 1369:#ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE
        -: 1370:#define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params)
        -: 1371:
        -: 1372:/* prototype to pass -Wmissing-prototypes */
        -: 1373:STATIC void
        -: 1374:S_croak_xs_usage(const CV *const cv, const char *const params);
        -: 1375:
        -: 1376:STATIC void
        -: 1377:S_croak_xs_usage(const CV *const cv, const char *const params)
        -: 1378:{
        -: 1379:    const GV *const gv = CvGV(cv);
        -: 1380:
        -: 1381:    PERL_ARGS_ASSERT_CROAK_XS_USAGE;
        -: 1382:
        -: 1383:    if (gv) {
        -: 1384:        const char *const gvname = GvNAME(gv);
        -: 1385:        const HV *const stash = GvSTASH(gv);
        -: 1386:        const char *const hvname = stash ? HvNAME(stash) : NULL;
        -: 1387:
        -: 1388:        if (hvname)
        -: 1389:	    Perl_croak_nocontext("Usage: %s::%s(%s)", hvname, gvname, params);
        -: 1390:        else
        -: 1391:	    Perl_croak_nocontext("Usage: %s(%s)", gvname, params);
        -: 1392:    } else {
        -: 1393:        /* Pants. I don't think that it should be possible to get here. */
        -: 1394:	Perl_croak_nocontext("Usage: CODE(0x%" UVxf ")(%s)", PTR2UV(cv), params);
        -: 1395:    }
        -: 1396:}
        -: 1397:#undef  PERL_ARGS_ASSERT_CROAK_XS_USAGE
        -: 1398:
        -: 1399:#define croak_xs_usage        S_croak_xs_usage
        -: 1400:
        -: 1401:#endif
        -: 1402:
        -: 1403:/* NOTE: the prototype of newXSproto() is different in versions of perls,
        -: 1404: * so we define a portable version of newXSproto()
        -: 1405: */
        -: 1406:#ifdef newXS_flags
        -: 1407:#define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0)
        -: 1408:#else
        -: 1409:#define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv)
        -: 1410:#endif /* !defined(newXS_flags) */
        -: 1411:
        -: 1412:#if PERL_VERSION_LE(5, 21, 5)
        -: 1413:#  define newXS_deffile(a,b) Perl_newXS(aTHX_ a,b,file)
        -: 1414:#else
        -: 1415:#  define newXS_deffile(a,b) Perl_newXS_deffile(aTHX_ a,b)
        -: 1416:#endif
        -: 1417:
        -: 1418:/* simple backcompat versions of the TARGx() macros with no optimisation */
        -: 1419:#ifndef TARGi
        -: 1420:#  define TARGi(iv, do_taint) sv_setiv_mg(TARG, iv)
        -: 1421:#  define TARGu(uv, do_taint) sv_setuv_mg(TARG, uv)
        -: 1422:#  define TARGn(nv, do_taint) sv_setnv_mg(TARG, nv)
        -: 1423:#endif
        -: 1424:
        -: 1425:#line 1426 "LikeR.c"
        -: 1426:
        -: 1427:XS_EUPXS(XS_Stats__LikeR_ks_test); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_ks_test called 12 returned 100% blocks executed 77%
       12: 1428:XS_EUPXS(XS_Stats__LikeR_ks_test)
        -: 1429:{
       12: 1430:    dVAR; dXSARGS;
       12: 1430-block  0
call    0 returned 12
branch  1 taken 12 (fallthrough)
branch  2 taken 0
        -: 1431:    PERL_UNUSED_VAR(cv); /* -W */
        -: 1432:    PERL_UNUSED_VAR(items); /* -W */
        -: 1433:    {
        -: 1434:	SV *	RETVAL;
        -: 1435:#line 1269 "LikeR.xs"
        -: 1436:{
        -: 1437:	SV *restrict x_sv = NULL, *restrict y_sv = NULL;
        -: 1438:	short int exact = -1;
        -: 1439:	const char *restrict alternative = "two.sided";
        -: 1440:	int arg_idx = 0;
        -: 1441:
        -: 1442:	// Shift arrays if provided positionally
        -: 1443:	if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 1444:		x_sv = ST(arg_idx);
        -: 1445:		arg_idx++;
        -: 1446:	}
        -: 1447:	// Check if second argument is an array (2-sample) or a string representing a CDF (1-sample)
        -: 1448:	if (arg_idx < items) {
        -: 1449:		if (SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 1450:			y_sv = ST(arg_idx);
        -: 1451:			arg_idx++;
        -: 1452:		} else if (SvPOK(ST(arg_idx))) {
        -: 1453:			y_sv = ST(arg_idx); // Save string (e.g., "pnorm") for 1-sample test logic
        -: 1454:			arg_idx++;
        -: 1455:		}
        -: 1456:	}
        -: 1457:
        -: 1458:	// Parse named arguments
        -: 1459:	for (; arg_idx < items; arg_idx += 2) {
        -: 1460:	  const char *restrict key = SvPV_nolen(ST(arg_idx));
        -: 1461:	  SV *restrict val = ST(arg_idx + 1);
        -: 1462:	  if      (strEQ(key, "x"))           x_sv = val;
        -: 1463:	  else if (strEQ(key, "y"))           y_sv = val;
        -: 1464:	  else if (strEQ(key, "exact"))       {
        -: 1465:		   if (!SvOK(val)) exact = -1;
        -: 1466:		   else exact = SvTRUE(val) ? 1 : 0;
        -: 1467:	  }
        -: 1468:	  else if (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 1469:	  else croak("ks_test: unknown argument '%s'", key);
        -: 1470:	}
        -: 1471:
        -: 1472:	if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV) {
        -: 1473:	  croak("ks_test: 'x' is a required argument and must be an ARRAY reference");
        -: 1474:	}
        -: 1475:
        -: 1476:	bool is_two_sided = strEQ(alternative, "two.sided") ? 1 : 0;
        -: 1477:	bool is_greater   = strEQ(alternative, "greater") ? 1 : 0;
        -: 1478:	bool is_less      = strEQ(alternative, "less") ? 1 : 0;
        -: 1479:
        -: 1480:	if (!is_two_sided && !is_greater && !is_less) {
        -: 1481:	  croak("ks_test: alternative must be 'two.sided', 'less', or 'greater'");
        -: 1482:	}
        -: 1483:
        -: 1484:	AV *restrict x_av = (AV*)SvRV(x_sv);
        -: 1485:	size_t nx = av_len(x_av) + 1;
        -: 1486:	if (nx == 0) croak("Not enough 'x' observations");
        -: 1487:
        -: 1488:	// Extract 'x' array to C-array
        -: 1489:	double *restrict x_data = (double *)safemalloc(nx * sizeof(double));
        -: 1490:	size_t valid_nx = 0;
        -: 1491:	for (size_t i = 0; i < nx; i++) {
        -: 1492:	  SV**restrict el = av_fetch(x_av, i, 0);
        -: 1493:	  if (el && SvOK(*el) && looks_like_number(*el)) {
        -: 1494:		   x_data[valid_nx++] = SvNV(*el);
        -: 1495:	  }
        -: 1496:	}
        -: 1497:
        -: 1498:	double statistic = 0.0, p_value = 0.0;
        -: 1499:	const char *restrict method_desc = "";
        -: 1500:
        -: 1501:	// --- TWO SAMPLE ---
        -: 1502:	if (y_sv && SvROK(y_sv) && SvTYPE(SvRV(y_sv)) == SVt_PVAV) {
        -: 1503:	  AV *restrict y_av = (AV*)SvRV(y_sv);
        -: 1504:	  size_t ny = av_len(y_av) + 1;
        -: 1505:
        -: 1506:	  double *restrict y_data = (double *)safemalloc(ny * sizeof(double));
        -: 1507:	  size_t valid_ny = 0;
        -: 1508:	  for (size_t i = 0; i < ny; i++) {
        -: 1509:		   SV**restrict el = av_fetch(y_av, i, 0);
        -: 1510:		   if (el && SvOK(*el) && looks_like_number(*el)) {
        -: 1511:		       y_data[valid_ny++] = SvNV(*el);
        -: 1512:		   }
        -: 1513:	  }
        -: 1514:
        -: 1515:	  if (valid_nx < 1 || valid_ny < 1) {
        -: 1516:		   Safefree(x_data); Safefree(y_data);
        -: 1517:		   croak("Not enough non-missing observations for KS test");
        -: 1518:	  }
        -: 1519:
        -: 1520:	  double d, d_plus, d_minus;
        -: 1521:	  calc_2sample_stats(x_data, valid_nx, y_data, valid_ny, &d, &d_plus, &d_minus);
        -: 1522:
        -: 1523:	  // Map alternative to the correct statistic
        -: 1524:	  if (is_greater) statistic = d_plus;
        -: 1525:	  else if (is_less) statistic = d_minus;
        -: 1526:	  else statistic = d;
        -: 1527:
        -: 1528:	  // Determine if exact or asymptotic
        -: 1529:	  bool use_exact = false;
        -: 1530:	  if (exact == 1) use_exact = true;
        -: 1531:	  else if (exact == 0) use_exact = false;
        -: 1532:	  else use_exact = (valid_nx * valid_ny < 10000); 
        -: 1533:
        -: 1534:	  // Check for ties in combined set
        -: 1535:	  size_t total_n = valid_nx + valid_ny;
        -: 1536:	  double *restrict comb = (double *)safemalloc(total_n * sizeof(double));
        -: 1537:	  for(size_t i=0; i<valid_nx; i++) comb[i] = x_data[i];
        -: 1538:	  for(size_t i=0; i<valid_ny; i++) comb[valid_nx+i] = y_data[i];
        -: 1539:	  qsort(comb, total_n, sizeof(double), compare_doubles);
        -: 1540:
        -: 1541:	  bool has_ties = false;
        -: 1542:	  for(size_t i = 1; i < total_n; i++) {
        -: 1543:		   if(comb[i] == comb[i-1]) { has_ties = true; break; }
        -: 1544:	  }
        -: 1545:	  Safefree(comb);
        -: 1546:	  if (use_exact && has_ties) {
        -: 1547:		   warn("cannot compute exact p-value with ties; falling back to asymptotic");
        -: 1548:		   use_exact = false;
        -: 1549:	  }
        -: 1550:	  if (use_exact) {
        -: 1551:		   method_desc = "Two-sample Kolmogorov-Smirnov exact test";
        -: 1552:		   double q = (0.5 + floor(statistic * valid_nx * valid_ny - 1e-7)) / ((double)valid_nx * valid_ny);
        -: 1553:		   p_value = psmirnov_exact_uniq_upper(q, valid_nx, valid_ny, is_two_sided);
        -: 1554:	  } else {
        -: 1555:		   method_desc = "Two-sample Kolmogorov-Smirnov test (asymptotic)";
        -: 1556:		   double z = statistic * sqrt((double)(valid_nx * valid_ny) / (valid_nx + valid_ny));
        -: 1557:		   if (is_two_sided) {
        -: 1558:		       p_value = K2l(z, 0, 1e-9); 
        -: 1559:		   } else {
        -: 1560:		       p_value = exp(-2.0 * z * z); // One-sided limit distribution
        -: 1561:		   }
        -: 1562:	  }
        -: 1563:	  Safefree(y_data);
        -: 1564:	} 
        -: 1565:	// --- ONE SAMPLE (e.g. against pnorm) ---
        -: 1566:	else if (y_sv && SvPOK(y_sv)) {
        -: 1567:	  const char *restrict dist = SvPV_nolen(y_sv);
        -: 1568:	  if (strEQ(dist, "pnorm")) {
        -: 1569:		   qsort(x_data, valid_nx, sizeof(double), compare_doubles);
        -: 1570:		   double max_d = 0.0, max_d_plus = 0.0, max_d_minus = 0.0;
        -: 1571:		   for(size_t i = 0; i < valid_nx; i++) {
        -: 1572:		       double cdf_obs_low  = (double)i / valid_nx;
        -: 1573:		       double cdf_obs_high = (double)(i + 1) / valid_nx;
        -: 1574:		       double cdf_theor    = approx_pnorm(x_data[i]);
        -: 1575:
        -: 1576:		       double diff1 = cdf_obs_low - cdf_theor;
        -: 1577:		       double diff2 = cdf_obs_high - cdf_theor;
        -: 1578:
        -: 1579:		       if (diff1 > max_d_plus) max_d_plus = diff1;
        -: 1580:		       if (diff2 > max_d_plus) max_d_plus = diff2;
        -: 1581:		       if (-diff1 > max_d_minus) max_d_minus = -diff1;
        -: 1582:		       if (-diff2 > max_d_minus) max_d_minus = -diff2;
        -: 1583:
        -: 1584:		       if (fabs(diff1) > max_d) max_d = fabs(diff1);
        -: 1585:		       if (fabs(diff2) > max_d) max_d = fabs(diff2);
        -: 1586:		   }
        -: 1587:		   if (is_greater) statistic = max_d_plus;
        -: 1588:		   else if (is_less) statistic = max_d_minus;
        -: 1589:		   else statistic = max_d;
        -: 1590:		   bool use_exact = (exact == -1) ? (valid_nx < 100) : (exact == 1);
        -: 1591:		   if (use_exact) {
        -: 1592:		       method_desc = "One-sample Kolmogorov-Smirnov exact test";
        -: 1593:		       if (is_two_sided) {
        -: 1594:		           p_value = 1.0 - K2x(valid_nx, statistic);
        -: 1595:		       } else {
        -: 1596:		           warn("exact 1-sample 1-sided KS test not implemented; using asymptotic");
        -: 1597:		           double z = statistic * sqrt((double)valid_nx);
        -: 1598:		           p_value = exp(-2.0 * z * z);
        -: 1599:		       }
        -: 1600:		   } else {
        -: 1601:		       method_desc = "One-sample Kolmogorov-Smirnov test (asymptotic)";
        -: 1602:		       double z = statistic * sqrt((double)valid_nx);
        -: 1603:		       if (is_two_sided) p_value = K2l(z, 0, 1e-6); 
        -: 1604:		       else p_value = exp(-2.0 * z * z);
        -: 1605:		   }
        -: 1606:	  } else {
        -: 1607:		    Safefree(x_data);
        -: 1608:		    croak("ks_test: Unsupported 1-sample distribution '%s'. Use arrays for 2-sample.", dist);
        -: 1609:	  }
        -: 1610:	} else {
        -: 1611:	  Safefree(x_data);
        -: 1612:	  croak("ks_test: Invalid arguments for 'y'.");
        -: 1613:	}
        -: 1614:	Safefree(x_data);
        -: 1615:	if (p_value > 1.0) p_value = 1.0;
        -: 1616:	if (p_value < 0.0) p_value = 0.0;
        -: 1617:	HV *restrict res = newHV();
        -: 1618:	hv_stores(res, "statistic", newSVnv(statistic));
        -: 1619:	hv_stores(res, "p_value", newSVnv(p_value));
        -: 1620:	hv_stores(res, "method", newSVpv(method_desc, 0));
        -: 1621:	hv_stores(res, "alternative", newSVpv(alternative, 0));
        -: 1622:	RETVAL = newRV_noinc((SV*)res);
        -: 1623:}
        -: 1624:#line 1625 "LikeR.c"
       12: 1625:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 12
       12: 1626:	ST(0) = RETVAL;
        -: 1627:    }
       12: 1628:    XSRETURN(1);
        -: 1629:}
        -: 1630:
        -: 1631:
        -: 1632:XS_EUPXS(XS_Stats__LikeR_wilcox_test); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_wilcox_test called 30 returned 80% blocks executed 72%
       30: 1633:XS_EUPXS(XS_Stats__LikeR_wilcox_test)
        -: 1634:{
       30: 1635:    dVAR; dXSARGS;
       30: 1635-block  0
call    0 returned 30
branch  1 taken 30 (fallthrough)
branch  2 taken 0
        -: 1636:    PERL_UNUSED_VAR(cv); /* -W */
        -: 1637:    PERL_UNUSED_VAR(items); /* -W */
        -: 1638:    {
        -: 1639:	SV *	RETVAL;
        -: 1640:#line 1462 "LikeR.xs"
        -: 1641:{
        -: 1642:	SV *restrict x_sv = NULL, *restrict y_sv = NULL;
        -: 1643:	bool paired = false, correct = true;
        -: 1644:	double mu = 0.0;
        -: 1645:	short int exact = -1;
        -: 1646:	const char *restrict alternative = "two.sided";
        -: 1647:	int arg_idx = 0;
        -: 1648:	// 1. Shift first positional argument as 'x' if it's an array reference
        -: 1649:	if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 1650:		x_sv = ST(arg_idx);
        -: 1651:		arg_idx++;
        -: 1652:	}
        -: 1653:	// 2. Shift second positional argument as 'y' if it's an array reference
        -: 1654:	if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 1655:		y_sv = ST(arg_idx);
        -: 1656:		arg_idx++;
        -: 1657:	}
        -: 1658:	// Ensure the remaining arguments form complete key-value pairs
        -: 1659:	if ((items - arg_idx) % 2 != 0) {
        -: 1660:		croak("Usage: wilcox_test(\\@x, [\\@y], key => value, ...)");
        -: 1661:	}
        -: 1662:	// --- Parse named arguments from the remaining flat stack ---
        -: 1663:	for (; arg_idx < items; arg_idx += 2) {
        -: 1664:		const char *restrict key = SvPV_nolen(ST(arg_idx));
        -: 1665:		SV *restrict val = ST(arg_idx + 1);
        -: 1666:		if      (strEQ(key, "x"))           x_sv = val;
        -: 1667:		else if (strEQ(key, "y"))           y_sv = val;
        -: 1668:		else if (strEQ(key, "paired"))      paired = SvTRUE(val);
        -: 1669:		else if (strEQ(key, "correct"))     correct = SvTRUE(val);
        -: 1670:		else if (strEQ(key, "mu"))          mu = SvNV(val);
        -: 1671:		else if (strEQ(key, "exact"))       {
        -: 1672:			if (!SvOK(val)) exact = -1;
        -: 1673:			else exact = SvTRUE(val) ? 1 : 0;
        -: 1674:		}
        -: 1675:		else if (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 1676:		else croak("wilcox_test: unknown argument '%s'", key);
        -: 1677:	}
        -: 1678:	// --- Validate required / types ---
        -: 1679:	if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 1680:		croak("wilcox_test: 'x' is a required argument and must be an ARRAY reference");
        -: 1681:	AV *restrict x_av = (AV*)SvRV(x_sv);
        -: 1682:	size_t nx = av_len(x_av) + 1;
        -: 1683:	if (nx == 0) croak("Not enough 'x' observations");
        -: 1684:
        -: 1685:	AV *restrict y_av = NULL;
        -: 1686:	size_t ny = 0;
        -: 1687:	if (y_sv && SvROK(y_sv) && SvTYPE(SvRV(y_sv)) == SVt_PVAV) {
        -: 1688:		y_av = (AV*)SvRV(y_sv);
        -: 1689:		ny = av_len(y_av) + 1;
        -: 1690:	}
        -: 1691:	double p_value = 0.0, statistic = 0.0;
        -: 1692:	const char *restrict method_desc = "";
        -: 1693:	bool use_exact = false;
        -: 1694:	// --- TWO SAMPLE (Mann-Whitney) ---
        -: 1695:	if (ny > 0 && !paired) {
        -: 1696:		RankInfo *restrict ri = (RankInfo *)safemalloc((nx + ny) * sizeof(RankInfo));
        -: 1697:		size_t valid_nx = 0, valid_ny = 0;
        -: 1698:		for (size_t i = 0; i < nx; i++) {
        -: 1699:			SV**restrict el = av_fetch(x_av, i, 0);
        -: 1700:			if (el && SvOK(*el) && looks_like_number(*el)) {
        -: 1701:				ri[valid_nx].val = SvNV(*el) - mu; // R subtracts mu from x
        -: 1702:				ri[valid_nx].idx = 1;
        -: 1703:				valid_nx++;
        -: 1704:			}
        -: 1705:		}
        -: 1706:		for (size_t i = 0; i < ny; i++) {
        -: 1707:			SV**restrict el = av_fetch(y_av, i, 0);
        -: 1708:			if (el && SvOK(*el) && looks_like_number(*el)) {
        -: 1709:				ri[valid_nx + valid_ny].val = SvNV(*el);
        -: 1710:				ri[valid_nx + valid_ny].idx = 2;
        -: 1711:				valid_ny++;
        -: 1712:			}
        -: 1713:		}
        -: 1714:		if (valid_nx == 0) { Safefree(ri); croak("not enough (non-missing) 'x' observations"); }
        -: 1715:		if (valid_ny == 0) { Safefree(ri); croak("not enough 'y' observations"); }
        -: 1716:		size_t total_n = valid_nx + valid_ny;
        -: 1717:		bool has_ties = 0;
        -: 1718:		double tie_adj = rank_and_count_ties(ri, total_n, &has_ties);
        -: 1719:		double w_rank_sum = 0.0;
        -: 1720:		for (size_t i = 0; i < total_n; i++) if (ri[i].idx == 1) w_rank_sum += ri[i].rank;
        -: 1721:		statistic = w_rank_sum - (double)valid_nx * (valid_nx + 1.0) / 2.0;
        -: 1722:
        -: 1723:		if (exact == 1) use_exact = true;
        -: 1724:		else if (exact == 0) use_exact = false;
        -: 1725:		else use_exact = (valid_nx < 50 && valid_ny < 50 && !has_ties);
        -: 1726:
        -: 1727:		if (use_exact && has_ties) {
        -: 1728:			warn("cannot compute exact p-value with ties; falling back to approximation");
        -: 1729:			use_exact = false;
        -: 1730:		}
        -: 1731:		if (use_exact) {
        -: 1732:			method_desc = "Wilcoxon rank sum exact test";
        -: 1733:			double p_less = exact_pwilcox(statistic, valid_nx, valid_ny);
        -: 1734:			double p_greater = 1.0 - exact_pwilcox(statistic - 1.0, valid_nx, valid_ny);
        -: 1735:
        -: 1736:			if (strcmp(alternative, "less") == 0) p_value = p_less;
        -: 1737:			else if (strcmp(alternative, "greater") == 0) p_value = p_greater;
        -: 1738:			else {
        -: 1739:				double p = (p_less < p_greater) ? p_less : p_greater;
        -: 1740:				p_value = 2.0 * p;
        -: 1741:			}
        -: 1742:		} else {
        -: 1743:			method_desc = correct ? "Wilcoxon rank sum test with continuity correction" : "Wilcoxon rank sum test";
        -: 1744:			double exp = (double)valid_nx * valid_ny / 2.0;
        -: 1745:			double var = ((double)valid_nx * valid_ny / 12.0) * ((total_n + 1.0) - tie_adj / (total_n * (total_n - 1.0)));
        -: 1746:			double z = statistic - exp;
        -: 1747:
        -: 1748:			double CORRECTION = 0.0;
        -: 1749:			if (correct) {
        -: 1750:				if (strcmp(alternative, "two.sided") == 0) CORRECTION = (z > 0 ? 0.5 : -0.5);
        -: 1751:				else if (strcmp(alternative, "greater") == 0) CORRECTION = 0.5;
        -: 1752:				else if (strcmp(alternative, "less") == 0) CORRECTION = -0.5;
        -: 1753:			}
        -: 1754:			z = (z - CORRECTION) / sqrt(var);
        -: 1755:
        -: 1756:			if (strcmp(alternative, "less") == 0) p_value = approx_pnorm(z);
        -: 1757:			else if (strcmp(alternative, "greater") == 0) p_value = 1.0 - approx_pnorm(z);
        -: 1758:			else p_value = 2.0 * approx_pnorm(-fabs(z));
        -: 1759:		}
        -: 1760:		Safefree(ri);
        -: 1761:	} else { // --- ONE SAMPLE / PAIRED ---
        -: 1762:		if (paired && (!y_av || nx != ny)) croak("'x' and 'y' must have the same length for paired test");
        -: 1763:		double *restrict diffs = (double *)safemalloc(nx * sizeof(double));
        -: 1764:		size_t n_nz = 0;
        -: 1765:		bool has_zeroes = false;
        -: 1766:		for (size_t i = 0; i < nx; i++) {
        -: 1767:			SV**restrict x_el = av_fetch(x_av, i, 0);
        -: 1768:			if (!x_el || !SvOK(*x_el) || !looks_like_number(*x_el)) continue;
        -: 1769:			double dx = SvNV(*x_el);
        -: 1770:
        -: 1771:			if (paired) {
        -: 1772:				SV**restrict y_el = av_fetch(y_av, i, 0);
        -: 1773:				if (!y_el || !SvOK(*y_el) || !looks_like_number(*y_el)) continue;
        -: 1774:				double dy = SvNV(*y_el);
        -: 1775:				double d = dx - dy - mu;
        -: 1776:				if (d == 0.0) has_zeroes = true; // Drop exact zeroes
        -: 1777:				else diffs[n_nz++] = d;
        -: 1778:			} else {
        -: 1779:				double d = dx - mu;
        -: 1780:				if (d == 0.0) has_zeroes = true;
        -: 1781:				else diffs[n_nz++] = d;
        -: 1782:			}
        -: 1783:		}
        -: 1784:		if (n_nz == 0) {
        -: 1785:			Safefree(diffs);
        -: 1786:			croak("not enough (non-missing) observations");
        -: 1787:		}
        -: 1788:		RankInfo *ri = (RankInfo *)safemalloc(n_nz * sizeof(RankInfo));
        -: 1789:		for (size_t i = 0; i < n_nz; i++) { 
        -: 1790:			ri[i].val = fabs(diffs[i]); 
        -: 1791:			ri[i].idx = (diffs[i] > 0);
        -: 1792:		}
        -: 1793:		bool has_ties = 0;
        -: 1794:		double tie_adj = rank_and_count_ties(ri, n_nz, &has_ties);
        -: 1795:		statistic = 0.0;
        -: 1796:		for (size_t i = 0; i < n_nz; i++) {
        -: 1797:			if (ri[i].idx) statistic += ri[i].rank;
        -: 1798:		}
        -: 1799:		if (exact == 1) use_exact = true;
        -: 1800:		else if (exact == 0) use_exact = false;
        -: 1801:		else use_exact = (n_nz < 50 && !has_ties);
        -: 1802:		if (use_exact && has_ties) {
        -: 1803:			warn("cannot compute exact p-value with ties; falling back to approximation");
        -: 1804:			use_exact = false;
        -: 1805:		}
        -: 1806:		if (use_exact && has_zeroes) {
        -: 1807:			warn("cannot compute exact p-value with zeroes; falling back to approximation");
        -: 1808:			use_exact = false;
        -: 1809:		}
        -: 1810:		if (use_exact) {
        -: 1811:			method_desc = paired ? "Wilcoxon exact signed rank test" : "Wilcoxon exact signed rank test";
        -: 1812:			double p_less = exact_psignrank(statistic, n_nz);
        -: 1813:			double p_greater = 1.0 - exact_psignrank(statistic - 1.0, n_nz);
        -: 1814:
        -: 1815:			if (strcmp(alternative, "less") == 0) p_value = p_less;
        -: 1816:			else if (strcmp(alternative, "greater") == 0) p_value = p_greater;
        -: 1817:			else {
        -: 1818:				double p = (p_less < p_greater) ? p_less : p_greater;
        -: 1819:				p_value = 2.0 * p;
        -: 1820:			}
        -: 1821:		} else {
        -: 1822:			method_desc = correct ? "Wilcoxon signed rank test with continuity correction" : "Wilcoxon signed rank test";
        -: 1823:			double exp = (double)n_nz * (n_nz + 1.0) / 4.0;
        -: 1824:			double var = (n_nz * (n_nz + 1.0) * (2.0 * n_nz + 1.0) / 24.0) - (tie_adj / 48.0);
        -: 1825:			double z = statistic - exp;
        -: 1826:			double CORRECTION = 0.0;
        -: 1827:			if (correct) {
        -: 1828:				if (strcmp(alternative, "two.sided") == 0) CORRECTION = (z > 0 ? 0.5 : -0.5);
        -: 1829:				else if (strcmp(alternative, "greater") == 0) CORRECTION = 0.5;
        -: 1830:				else if (strcmp(alternative, "less") == 0) CORRECTION = -0.5;
        -: 1831:			}
        -: 1832:			z = (z - CORRECTION) / sqrt(var);
        -: 1833:
        -: 1834:			if (strcmp(alternative, "less") == 0) p_value = approx_pnorm(z);
        -: 1835:			else if (strcmp(alternative, "greater") == 0) p_value = 1.0 - approx_pnorm(z);
        -: 1836:			else p_value = 2.0 * approx_pnorm(-fabs(z));
        -: 1837:		}
        -: 1838:		Safefree(ri); Safefree(diffs);
        -: 1839:	}
        -: 1840:	if (p_value > 1.0) p_value = 1.0;
        -: 1841:	HV *restrict res = newHV();
        -: 1842:	hv_stores(res, "statistic", newSVnv(statistic));
        -: 1843:	hv_stores(res, "p_value", newSVnv(p_value));
        -: 1844:	hv_stores(res, "method", newSVpv(method_desc, 0));
        -: 1845:	hv_stores(res, "alternative", newSVpv(alternative, 0));
        -: 1846:	RETVAL = newRV_noinc((SV*)res);
        -: 1847:}
        -: 1848:#line 1849 "LikeR.c"
       24: 1849:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 24
       24: 1850:	ST(0) = RETVAL;
        -: 1851:    }
       24: 1852:    XSRETURN(1);
        -: 1853:}
        -: 1854:
        -: 1855:
        -: 1856:XS_EUPXS(XS_Stats__LikeR__chisq_c); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR__chisq_c called 9 returned 100% blocks executed 94%
        9: 1857:XS_EUPXS(XS_Stats__LikeR__chisq_c)
        -: 1858:{
        9: 1859:    dVAR; dXSARGS;
        9: 1859-block  0
call    0 returned 9
        9: 1860:    if (items != 1)
branch  0 taken 0 (fallthrough)
branch  1 taken 9
    #####: 1861:       croak_xs_usage(cv,  "data_ref");
    %%%%%: 1861-block  0
call    0 never executed
        -: 1862:    {
        9: 1863:	SV*	data_ref = ST(0)
        9: 1863-block  0
branch  0 taken 0 (fallthrough)
branch  1 taken 9
        -: 1864:;
        -: 1865:	SV *	RETVAL;
        -: 1866:#line 1675 "LikeR.xs"
        -: 1867:{
        -: 1868:	AV*restrict obs_av = (AV*)SvRV(data_ref);
        -: 1869:	int r = av_top_index(obs_av) + 1, c = 0;
        -: 1870:	bool is_2d = 0;
        -: 1871:	SV**restrict first_elem = av_fetch(obs_av, 0, 0);
        -: 1872:	if (first_elem && SvROK(*first_elem) && SvTYPE(SvRV(*first_elem)) == SVt_PVAV) {
        -: 1873:		is_2d = 1;
        -: 1874:		AV*restrict first_row = (AV*)SvRV(*first_elem);
        -: 1875:		c = av_top_index(first_row) + 1;
        -: 1876:	} else {
        -: 1877:		c = r;
        -: 1878:		r = 1;
        -: 1879:	}
        -: 1880:
        -: 1881:	double stat = 0.0, grand_total = 0.0;
        -: 1882:	int df = 0;
        -: 1883:	int yates = (is_2d && r == 2 && c == 2) ? 1 : 0;
        -: 1884:
        -: 1885:	AV*restrict expected_av = newAV();
        -: 1886:	if (is_2d) {
        -: 1887:		double *restrict row_sum = (double*)safemalloc(r * sizeof(double));
        -: 1888:		double *restrict col_sum = (double*)safemalloc(c * sizeof(double));
        -: 1889:		for(unsigned int i=0; i<r; i++) row_sum[i] = 0.0;
        -: 1890:		for(unsigned int j=0; j<c; j++) col_sum[j] = 0.0;
        -: 1891:		for (unsigned int i = 0; i < r; i++) {
        -: 1892:			SV**restrict row_sv = av_fetch(obs_av, i, 0);
        -: 1893:			AV*restrict row = (AV*)SvRV(*row_sv);
        -: 1894:			for (unsigned int j = 0; j < c; j++) {
        -: 1895:				 SV**restrict val_sv = av_fetch(row, j, 0);
        -: 1896:				 double val = SvNV(*val_sv);
        -: 1897:				 row_sum[i] += val;
        -: 1898:				 col_sum[j] += val;
        -: 1899:				 grand_total += val;
        -: 1900:			}
        -: 1901:		}
        -: 1902:		for (unsigned int i = 0; i < r; i++) {
        -: 1903:			AV*restrict exp_row = newAV();
        -: 1904:			SV**restrict row_sv = av_fetch(obs_av, i, 0);
        -: 1905:			AV*restrict row = (AV*)SvRV(*row_sv);
        -: 1906:			for (unsigned int j = 0; j < c; j++) {
        -: 1907:				double E = (row_sum[i] * col_sum[j]) / grand_total;
        -: 1908:				SV**restrict val_sv = av_fetch(row, j, 0);
        -: 1909:				double O = SvNV(*val_sv);
        -: 1910:				av_push(exp_row, newSVnv(E));
        -: 1911:				if (yates) {
        -: 1912:				  // Exact R logic: min(0.5, abs(O - E))
        -: 1913:				  double abs_diff = fabs(O - E);
        -: 1914:				  double y_corr = (abs_diff > 0.5) ? 0.5 : abs_diff;
        -: 1915:				  double diff = abs_diff - y_corr;
        -: 1916:				  stat += (diff * diff) / E;
        -: 1917:				} else {
        -: 1918:				  stat += ((O - E) * (O - E)) / E;
        -: 1919:				}
        -: 1920:			}
        -: 1921:			av_push(expected_av, newRV_noinc((SV*)exp_row));
        -: 1922:		}
        -: 1923:		safefree(row_sum); safefree(col_sum);
        -: 1924:		df = (r - 1) * (c - 1);
        -: 1925:	} else {
        -: 1926:	  for (unsigned int j = 0; j < c; j++) {
        -: 1927:		   SV**restrict val_sv = av_fetch(obs_av, j, 0);
        -: 1928:		   grand_total += SvNV(*val_sv);
        -: 1929:	  }
        -: 1930:	  double E = grand_total / (double)c;
        -: 1931:	  for (unsigned int j = 0; j < c; j++) {
        -: 1932:		   SV**restrict val_sv = av_fetch(obs_av, j, 0);
        -: 1933:		   double O = SvNV(*val_sv);
        -: 1934:		   av_push(expected_av, newSVnv(E));
        -: 1935:		   stat += ((O - E) * (O - E)) / E;
        -: 1936:	  }
        -: 1937:	  df = c - 1;
        -: 1938:	}
        -: 1939:	double p_val = get_p_value(stat, df);
        -: 1940:	HV*restrict results = newHV();
        -: 1941:	hv_store(results, "statistic", 9, newSVnv(stat), 0);
        -: 1942:	hv_store(results, "df", 2, newSViv(df), 0);
        -: 1943:	hv_store(results, "p_value", 7, newSVnv(p_val), 0);
        -: 1944:	hv_store(results, "expected", 8, newRV_noinc((SV*)expected_av), 0);
        -: 1945:	if (is_2d) {
        -: 1946:		if (yates) {
        -: 1947:			hv_store(results, "method", 6, newSVpv("Pearson's Chi-squared test with Yates' continuity correction", 0), 0);
        -: 1948:		} else {
        -: 1949:			hv_store(results, "method", 6, newSVpv("Pearson's Chi-squared test", 0), 0);
        -: 1950:		}
        -: 1951:	} else {
        -: 1952:	  hv_store(results, "method", 6, newSVpv("Chi-squared test for given probabilities", 0), 0);
        -: 1953:	}
        -: 1954:	RETVAL = newRV_noinc((SV*)results);
        -: 1955:}
        -: 1956:#line 1957 "LikeR.c"
        9: 1957:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 9
        9: 1958:	ST(0) = RETVAL;
        -: 1959:    }
        9: 1960:    XSRETURN(1);
        -: 1961:}
        -: 1962:
        -: 1963:
        -: 1964:XS_EUPXS(XS_Stats__LikeR_write_table); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_write_table called 37 returned 84% blocks executed 78%
       37: 1965:XS_EUPXS(XS_Stats__LikeR_write_table)
        -: 1966:{
       37: 1967:    dVAR; dXSARGS;
       37: 1967-block  0
call    0 returned 37
        -: 1968:    PERL_UNUSED_VAR(cv); /* -W */
        -: 1969:    PERL_UNUSED_VAR(items); /* -W */
        -: 1970:    PERL_UNUSED_VAR(ax); /* -Wall */
       37: 1971:    SP -= items;
branch  0 taken 37 (fallthrough)
branch  1 taken 0
        -: 1972:    {
        -: 1973:#line 1771 "LikeR.xs"
        -: 1974:{
        -: 1975:	SV *restrict data_sv = NULL;
        -: 1976:	SV *restrict file_sv = NULL;
        -: 1977:	unsigned int arg_idx = 0;
        -: 1978:
        -: 1979:	// Mimic the Perl shift logic
        -: 1980:	if (arg_idx < items && SvROK(ST(arg_idx))) {
        -: 1981:	  int type = SvTYPE(SvRV(ST(arg_idx)));
        -: 1982:	  if (type == SVt_PVHV || type == SVt_PVAV) {
        -: 1983:		   data_sv = ST(arg_idx);
        -: 1984:		   arg_idx++;
        -: 1985:	  }
        -: 1986:	}
        -: 1987:	if (arg_idx < items) {
        -: 1988:	  file_sv = ST(arg_idx);
        -: 1989:	  arg_idx++;
        -: 1990:	}
        -: 1991:
        -: 1992:	const char *restrict sep = ",";
        -: 1993:	SV *restrict row_names_sv = sv_2mortal(newSViv(1));
        -: 1994:	SV *restrict col_names_sv = NULL;
        -: 1995:
        -: 1996:	// Read the remaining Hash-style arguments
        -: 1997:	for (; arg_idx < items; arg_idx += 2) {
        -: 1998:	  if (arg_idx + 1 >= items) croak("write_table: Odd number of arguments passed");
        -: 1999:	  const char *restrict key = SvPV_nolen(ST(arg_idx));
        -: 2000:	  SV *restrict val = ST(arg_idx + 1);
        -: 2001:	  if (strEQ(key, "data")) data_sv = val;
        -: 2002:	  else if (strEQ(key, "col.names")) col_names_sv = val;
        -: 2003:	  else if (strEQ(key, "file")) file_sv = val;
        -: 2004:	  else if (strEQ(key, "row.names")) row_names_sv = val;
        -: 2005:	  else if (strEQ(key, "sep")) sep = SvPV_nolen(val);
        -: 2006:	  else croak("write_table: Unknown arguments passed: %s", key);
        -: 2007:	}
        -: 2008:
        -: 2009:	if (!data_sv || !SvROK(data_sv)) {
        -: 2010:	  croak("write_table: 'data' must be a HASH or ARRAY reference\n");
        -: 2011:	}
        -: 2012:	SV *restrict data_ref = SvRV(data_sv);
        -: 2013:	if (SvTYPE(data_ref) != SVt_PVHV && SvTYPE(data_ref) != SVt_PVAV) {
        -: 2014:	  croak("write_table: 'data' must be a HASH or ARRAY reference\n");
        -: 2015:	}
        -: 2016:
        -: 2017:	if (!file_sv || !SvOK(file_sv)) croak("write_table: file name missing\n");
        -: 2018:	const char *restrict file = SvPV_nolen(file_sv);
        -: 2019:
        -: 2020:	if (col_names_sv && SvOK(col_names_sv)) {
        -: 2021:	  if (!SvROK(col_names_sv) || SvTYPE(SvRV(col_names_sv)) != SVt_PVAV) {
        -: 2022:		   croak("write_table: 'col.names' must be an ARRAY reference\n");
        -: 2023:	  }
        -: 2024:	}
        -: 2025:
        -: 2026:	bool is_hoh = 0, is_hoa = 0, is_aoh = 0;
        -: 2027:	AV *restrict rows_av = NULL;
        -: 2028:
        -: 2029:	// Validate Input Structures & Homogeneity 
        -: 2030:	if (SvTYPE(data_ref) == SVt_PVHV) {
        -: 2031:		HV *restrict hv = (HV*)data_ref;
        -: 2032:		if (hv_iterinit(hv) == 0) XSRETURN_EMPTY;
        -: 2033:
        -: 2034:		HE *restrict entry = hv_iternext(hv);
        -: 2035:		SV *restrict first_val = hv_iterval(hv, entry);
        -: 2036:		if (!first_val || !SvROK(first_val)) {
        -: 2037:			croak("write_table: Data values must be either all HASHes or all ARRAYs\n");
        -: 2038:		}
        -: 2039:		int first_type = SvTYPE(SvRV(first_val));
        -: 2040:		if (first_type != SVt_PVHV && first_type != SVt_PVAV) {
        -: 2041:			croak("write_table: Data values must be either all HASHes or all ARRAYs\n");
        -: 2042:		}
        -: 2043:
        -: 2044:		is_hoh = (first_type == SVt_PVHV);
        -: 2045:		is_hoa = (first_type == SVt_PVAV);
        -: 2046:
        -: 2047:		hv_iterinit(hv);
        -: 2048:		while ((entry = hv_iternext(hv))) {
        -: 2049:			SV *restrict val = hv_iterval(hv, entry);
        -: 2050:			if (!val || !SvROK(val) || SvTYPE(SvRV(val)) != first_type) {
        -: 2051:				 croak("write_table: Mixed data types detected. Ensure all values are %s references.\n", is_hoh ? "HASH" : "ARRAY");
        -: 2052:			}
        -: 2053:		}
        -: 2054:
        -: 2055:		if (is_hoh) {
        -: 2056:			rows_av = newAV();
        -: 2057:			hv_iterinit(hv);
        -: 2058:			while ((entry = hv_iternext(hv))) {
        -: 2059:				 av_push(rows_av, newSVsv(hv_iterkeysv(entry)));
        -: 2060:			}
        -: 2061:		}
        -: 2062:	} else {
        -: 2063:		AV *restrict av = (AV*)data_ref;
        -: 2064:		if (av_len(av) < 0) XSRETURN_EMPTY;
        -: 2065:		SV **restrict first_ptr = av_fetch(av, 0, 0);
        -: 2066:		if (!first_ptr || !*first_ptr || !SvROK(*first_ptr) || SvTYPE(SvRV(*first_ptr)) != SVt_PVHV) {
        -: 2067:			croak("write_table: For ARRAY data, all elements must be HASH references (Array of Hashes)\n");
        -: 2068:		}
        -: 2069:
        -: 2070:		for (size_t i = 0; i <= av_len(av); i++) {
        -: 2071:			SV **restrict ptr = av_fetch(av, i, 0);
        -: 2072:			if (!ptr || !*ptr || !SvROK(*ptr) || SvTYPE(SvRV(*ptr)) != SVt_PVHV) {
        -: 2073:				 croak("write_table: Mixed data types detected in Array of Hashes. All elements must be HASH references.\n");
        -: 2074:			}
        -: 2075:		}
        -: 2076:		is_aoh = 1;
        -: 2077:	}
        -: 2078:
        -: 2079:	PerlIO *restrict fh = PerlIO_open(file, "w");
        -: 2080:	if (!fh) croak("write_table: Could not open '%s' for writing", file);
        -: 2081:
        -: 2082:	AV *restrict headers_av = newAV();
        -: 2083:	int inc_rownames = (row_names_sv && SvTRUE(row_names_sv)) ? 1 : 0;
        -: 2084:	const char *restrict rownames_col = NULL;
        -: 2085:
        -: 2086:	// ----- Hash of Hashes -----
        -: 2087:	if (is_hoh) {
        -: 2088:		if (col_names_sv && SvOK(col_names_sv)) {
        -: 2089:			AV *restrict c_av = (AV*)SvRV(col_names_sv);
        -: 2090:			for(size_t i=0; i<=av_len(c_av); i++) {
        -: 2091:				SV **restrict c = av_fetch(c_av, i, 0);
        -: 2092:				if(c && SvOK(*c)) av_push(headers_av, newSVsv(*c));
        -: 2093:			}
        -: 2094:		} else {
        -: 2095:			HV *restrict col_map = newHV();
        -: 2096:			hv_iterinit((HV*)data_ref);
        -: 2097:			HE *restrict entry;
        -: 2098:			while((entry = hv_iternext((HV*)data_ref))) {
        -: 2099:				 HV *restrict inner = (HV*)SvRV(hv_iterval((HV*)data_ref, entry));
        -: 2100:				 hv_iterinit(inner);
        -: 2101:				 HE *restrict inner_entry;
        -: 2102:				 while((inner_entry = hv_iternext(inner))) {
        -: 2103:				     hv_store_ent(col_map, hv_iterkeysv(inner_entry), newSViv(1), 0);
        -: 2104:				 }
        -: 2105:			}
        -: 2106:			unsigned num_cols = hv_iterinit(col_map);
        -: 2107:			const char **restrict col_array = safemalloc(num_cols * sizeof(char*));
        -: 2108:			for(unsigned i=0; i<num_cols; i++) {
        -: 2109:				 HE *restrict ce = hv_iternext(col_map);
        -: 2110:				 col_array[i] = SvPV_nolen(hv_iterkeysv(ce));
        -: 2111:			}
        -: 2112:			qsort(col_array, num_cols, sizeof(char*), cmp_string_wt);
        -: 2113:			for(unsigned i=0; i<num_cols; i++) av_push(headers_av, newSVpv(col_array[i], 0));
        -: 2114:			safefree(col_array);
        -: 2115:			SvREFCNT_dec(col_map);
        -: 2116:	  }
        -: 2117:	  size_t num_headers = av_len(headers_av) + 1;
        -: 2118:	  const char **restrict header_row = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2119:
        -: 2120:	  size_t h_idx = 0;
        -: 2121:	  if (inc_rownames) header_row[h_idx++] = "";
        -: 2122:	  for(unsigned short int i=0; i<num_headers; i++) {
        -: 2123:		   SV**restrict h_ptr = av_fetch(headers_av, i, 0);
        -: 2124:		   header_row[h_idx++] = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2125:	  }
        -: 2126:	  print_string_row(fh, header_row, h_idx, sep);
        -: 2127:	  safefree(header_row);
        -: 2128:
        -: 2129:	  size_t num_rows = av_len(rows_av) + 1;
        -: 2130:	  const char **restrict row_array = safemalloc(num_rows * sizeof(char*));
        -: 2131:	  for(size_t i=0; i<num_rows; i++) {
        -: 2132:	  	row_array[i] = SvPV_nolen(*av_fetch(rows_av, i, 0));
        -: 2133:	  }
        -: 2134:	  qsort(row_array, num_rows, sizeof(char*), cmp_string_wt);
        -: 2135:
        -: 2136:	  HV *restrict data_hv = (HV*)data_ref;
        -: 2137:	  const char **restrict row_data = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2138:
        -: 2139:	  for(size_t i=0; i<num_rows; i++) {
        -: 2140:		   size_t d_idx = 0;
        -: 2141:		   if (inc_rownames) row_data[d_idx++] = row_array[i];
        -: 2142:
        -: 2143:		   SV **restrict inner_hv_ptr = hv_fetch(data_hv, row_array[i], strlen(row_array[i]), 0);
        -: 2144:		   HV *restrict inner_hv = inner_hv_ptr ? (HV*)SvRV(*inner_hv_ptr) : NULL;
        -: 2145:
        -: 2146:		   for(size_t j=0; j<num_headers; j++) {
        -: 2147:		       SV**restrict h_ptr = av_fetch(headers_av, j, 0);
        -: 2148:		       const char *restrict col_name = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2149:		       SV **restrict cell_ptr = inner_hv ? hv_fetch(inner_hv, col_name, strlen(col_name), 0) : NULL;
        -: 2150:		       if (cell_ptr && SvOK(*cell_ptr)) {
        -: 2151:		           if (SvROK(*cell_ptr)) {
        -: 2152:		               PerlIO_close(fh);
        -: 2153:		               safefree(row_array);
        -: 2154:                     safefree(row_data);
        -: 2155:                       if (headers_av) SvREFCNT_dec(headers_av);
        -: 2156:                       if (rows_av) SvREFCNT_dec(rows_av);
        -: 2157:		               croak("write_table: Cannot write nested reference types to table\n");
        -: 2158:		           }
        -: 2159:		           row_data[d_idx++] = SvPV_nolen(*cell_ptr);
        -: 2160:		       } else {
        -: 2161:		           row_data[d_idx++] = "NA";
        -: 2162:		       }
        -: 2163:		   }
        -: 2164:		   print_string_row(fh, row_data, d_idx, sep);
        -: 2165:	  }
        -: 2166:	  safefree(row_array); safefree(row_data);
        -: 2167:
        -: 2168:	} else if (is_hoa) { // ----- Hash of Arrays -----
        -: 2169:		HV *restrict data_hv = (HV*)data_ref;
        -: 2170:		size_t max_rows = 0;
        -: 2171:		hv_iterinit(data_hv);
        -: 2172:		HE *restrict entry;
        -: 2173:		while((entry = hv_iternext(data_hv))) {
        -: 2174:			AV *restrict arr = (AV*)SvRV(hv_iterval(data_hv, entry));
        -: 2175:			size_t len = av_len(arr) + 1;
        -: 2176:			if (len > max_rows) max_rows = len;
        -: 2177:		}
        -: 2178:
        -: 2179:		if (col_names_sv && SvOK(col_names_sv)) {
        -: 2180:			AV *restrict c_av = (AV*)SvRV(col_names_sv);
        -: 2181:			for(size_t i=0; i<=av_len(c_av); i++) {
        -: 2182:				 SV **restrict c = av_fetch(c_av, i, 0);
        -: 2183:				 if(c && SvOK(*c)) av_push(headers_av, newSVsv(*c));
        -: 2184:			}
        -: 2185:		} else {
        -: 2186:			unsigned int num_cols = hv_iterinit(data_hv);
        -: 2187:			const char **restrict col_array = safemalloc(num_cols * sizeof(char*));
        -: 2188:			for(unsigned int i=0; i<num_cols; i++) {
        -: 2189:				 HE *restrict ce = hv_iternext(data_hv);
        -: 2190:				 col_array[i] = SvPV_nolen(hv_iterkeysv(ce));
        -: 2191:			}
        -: 2192:			qsort(col_array, num_cols, sizeof(char*), cmp_string_wt);
        -: 2193:			for(unsigned i=0; i<num_cols; i++) av_push(headers_av, newSVpv(col_array[i], 0));
        -: 2194:			safefree(col_array);
        -: 2195:		}
        -: 2196:		if (av_len(headers_av) < 0) croak("Could not get headers in write_table");
        -: 2197:		if (inc_rownames && contains_nondigit(row_names_sv)) {
        -: 2198:			rownames_col = SvPV_nolen(row_names_sv);
        -: 2199:			AV *restrict filtered_headers = (AV*)sv_2mortal((SV*)newAV());
        -: 2200:
        -: 2201:			for(size_t i=0; i<=av_len(headers_av); i++) {
        -: 2202:				 SV**restrict h_ptr = av_fetch(headers_av, i, 0);
        -: 2203:				 if (!h_ptr || !*h_ptr) continue;
        -: 2204:				 SV *restrict h_sv = *h_ptr;
        -: 2205:				 if (strcmp(SvPV_nolen(h_sv), rownames_col) != 0) {
        -: 2206:				     av_push(filtered_headers, newSVsv(h_sv));
        -: 2207:				 }
        -: 2208:			}
        -: 2209:			SvREFCNT_dec(headers_av);
        -: 2210:			headers_av = filtered_headers;
        -: 2211:		}
        -: 2212:		size_t num_headers = av_len(headers_av) + 1;
        -: 2213:		const char **restrict header_row = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2214:		size_t h_idx = 0;
        -: 2215:		if (inc_rownames) header_row[h_idx++] = "";
        -: 2216:		for(size_t i=0; i<num_headers; i++) {
        -: 2217:			SV**restrict h_ptr = av_fetch(headers_av, i, 0);
        -: 2218:			header_row[h_idx++] = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2219:		}
        -: 2220:		print_string_row(fh, header_row, h_idx, sep);
        -: 2221:		safefree(header_row);
        -: 2222:		const char **restrict row_data = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2223:		for(size_t i=0; i<max_rows; i++) {
        -: 2224:			size_t d_idx = 0;
        -: 2225:			if (inc_rownames) {
        -: 2226:				 if (rownames_col) {
        -: 2227:				     SV **restrict rn_arr_ptr = hv_fetch(data_hv, rownames_col, strlen(rownames_col), 0);
        -: 2228:				     if (rn_arr_ptr && SvROK(*rn_arr_ptr)) {
        -: 2229:				         AV *restrict rn_arr = (AV*)SvRV(*rn_arr_ptr);
        -: 2230:				         SV **restrict rn_val_ptr = av_fetch(rn_arr, i, 0);
        -: 2231:				         if (rn_val_ptr && SvOK(*rn_val_ptr)) {
        -: 2232:				             if (SvROK(*rn_val_ptr)) {
        -: 2233:				                 PerlIO_close(fh);
        -: 2234:				                 safefree(row_data);
        -: 2235:				                 if (headers_av) SvREFCNT_dec(headers_av);
        -: 2236:				                 croak("write_table: Cannot write nested reference types to table\n");
        -: 2237:				             }
        -: 2238:				             row_data[d_idx++] = SvPV_nolen(*rn_val_ptr);
        -: 2239:				         } else {
        -: 2240:				             row_data[d_idx++] = "NA";
        -: 2241:				         }
        -: 2242:				     } else {
        -: 2243:				         row_data[d_idx++] = "NA";
        -: 2244:				     }
        -: 2245:				 } else {
        -: 2246:				     char buf[32];
        -: 2247:				     snprintf(buf, sizeof(buf), "%ld", (long)(i + 1));
        -: 2248:				     row_data[d_idx++] = savepv(buf);
        -: 2249:				 }
        -: 2250:			}
        -: 2251:			for(size_t j=0; j<num_headers; j++) {
        -: 2252:				 SV**restrict h_ptr = av_fetch(headers_av, j, 0);
        -: 2253:				 const char *restrict col_name = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2254:				 SV **restrict arr_ptr = hv_fetch(data_hv, col_name, strlen(col_name), 0);
        -: 2255:				 if (arr_ptr && SvROK(*arr_ptr)) {
        -: 2256:				     AV *restrict arr = (AV*)SvRV(*arr_ptr);
        -: 2257:				     SV **restrict cell_ptr = av_fetch(arr, i, 0);
        -: 2258:				     if (cell_ptr && SvOK(*cell_ptr)) {
        -: 2259:				         if (SvROK(*cell_ptr)) {
        -: 2260:				             PerlIO_close(fh);
        -: 2261:				             safefree(row_data);
        -: 2262:				             if (headers_av) SvREFCNT_dec(headers_av);
        -: 2263:				             croak("write_table: Cannot write nested reference types to table\n");
        -: 2264:				         }
        -: 2265:				         row_data[d_idx++] = SvPV_nolen(*cell_ptr);
        -: 2266:				     } else {
        -: 2267:				         row_data[d_idx++] = "NA";
        -: 2268:				     }
        -: 2269:				 } else {
        -: 2270:				     row_data[d_idx++] = "NA";
        -: 2271:				 }
        -: 2272:			}
        -: 2273:			print_string_row(fh, row_data, d_idx, sep);
        -: 2274:			if (inc_rownames && !rownames_col) safefree((char*)row_data[0]);
        -: 2275:		}
        -: 2276:		safefree(row_data);
        -: 2277:	} else if (is_aoh) {// ----- Array of Hashes -----
        -: 2278:		AV *restrict data_av = (AV*)data_ref;
        -: 2279:		size_t num_rows = av_len(data_av) + 1;
        -: 2280:		if (col_names_sv && SvOK(col_names_sv)) {
        -: 2281:			AV *restrict c_av = (AV*)SvRV(col_names_sv);
        -: 2282:			for(size_t i=0; i<=av_len(c_av); i++) {
        -: 2283:				 SV **restrict c = av_fetch(c_av, i, 0);
        -: 2284:				 if(c && SvOK(*c)) av_push(headers_av, newSVsv(*c));
        -: 2285:			}
        -: 2286:		} else {
        -: 2287:			HV *restrict col_map = newHV();
        -: 2288:			for(size_t i=0; i<num_rows; i++) {
        -: 2289:				 SV **restrict row_ptr = av_fetch(data_av, i, 0);
        -: 2290:				 if (row_ptr && SvROK(*row_ptr)) {
        -: 2291:				     HV *restrict row_hv = (HV*)SvRV(*row_ptr);
        -: 2292:				     hv_iterinit(row_hv);
        -: 2293:				     HE *restrict entry;
        -: 2294:				     while((entry = hv_iternext(row_hv))) {
        -: 2295:				         hv_store_ent(col_map, hv_iterkeysv(entry), newSViv(1), 0);
        -: 2296:				     }
        -: 2297:				 }
        -: 2298:			}
        -: 2299:			unsigned num_cols = hv_iterinit(col_map);
        -: 2300:			const char **restrict col_array = safemalloc(num_cols * sizeof(char*));
        -: 2301:			for(unsigned int i=0; i<num_cols; i++) {
        -: 2302:				 HE *restrict ce = hv_iternext(col_map);
        -: 2303:				 col_array[i] = SvPV_nolen(hv_iterkeysv(ce));
        -: 2304:			}
        -: 2305:			qsort(col_array, num_cols, sizeof(char*), cmp_string_wt);
        -: 2306:			for(unsigned int i=0; i<num_cols; i++) av_push(headers_av, newSVpv(col_array[i], 0));
        -: 2307:			safefree(col_array);
        -: 2308:			SvREFCNT_dec(col_map);
        -: 2309:		}
        -: 2310:		if (inc_rownames && contains_nondigit(row_names_sv)) {
        -: 2311:			rownames_col = SvPV_nolen(row_names_sv);
        -: 2312:			AV *restrict filtered_headers = newAV();
        -: 2313:			for(size_t i=0; i<=av_len(headers_av); i++) {
        -: 2314:				 SV**restrict h_ptr = av_fetch(headers_av, i, 0);
        -: 2315:				 if (!h_ptr || !*h_ptr) continue;
        -: 2316:				 SV *restrict h_sv = *h_ptr;
        -: 2317:				 if (strcmp(SvPV_nolen(h_sv), rownames_col) != 0) {
        -: 2318:				     av_push(filtered_headers, newSVsv(h_sv));
        -: 2319:				 }
        -: 2320:			}
        -: 2321:			SvREFCNT_dec(headers_av);
        -: 2322:			headers_av = filtered_headers;
        -: 2323:		}
        -: 2324:		size_t num_headers = av_len(headers_av) + 1;
        -: 2325:		const char **restrict header_row = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2326:		size_t h_idx = 0;
        -: 2327:		if (inc_rownames) header_row[h_idx++] = "";
        -: 2328:		for(size_t i=0; i<num_headers; i++) {
        -: 2329:			SV**restrict h_ptr = av_fetch(headers_av, i, 0);
        -: 2330:			header_row[h_idx++] = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2331:		}
        -: 2332:		print_string_row(fh, header_row, h_idx, sep);
        -: 2333:		safefree(header_row);
        -: 2334:		const char **restrict row_data = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2335:		for(size_t i=0; i<num_rows; i++) {
        -: 2336:			size_t d_idx = 0;
        -: 2337:			SV **restrict row_ptr = av_fetch(data_av, i, 0);
        -: 2338:			HV *restrict row_hv = (row_ptr && SvROK(*row_ptr)) ? (HV*)SvRV(*row_ptr) : NULL;
        -: 2339:			if (inc_rownames) {
        -: 2340:				if (rownames_col) {
        -: 2341:				  SV **restrict rn_val_ptr = row_hv ? hv_fetch(row_hv, rownames_col, strlen(rownames_col), 0) : NULL;
        -: 2342:				  if (rn_val_ptr && SvOK(*rn_val_ptr)) {
        -: 2343:						if (SvROK(*rn_val_ptr)) {
        -: 2344:							 PerlIO_close(fh);
        -: 2345:								  safefree(row_data);
        -: 2346:								  if (headers_av) SvREFCNT_dec(headers_av);
        -: 2347:							 croak("write_table: Cannot write nested reference types to table\n");
        -: 2348:						}
        -: 2349:						row_data[d_idx++] = SvPV_nolen(*rn_val_ptr);
        -: 2350:				  } else {
        -: 2351:						row_data[d_idx++] = "NA";
        -: 2352:				  }
        -: 2353:				} else {
        -: 2354:				  char buf[32];
        -: 2355:				  snprintf(buf, sizeof(buf), "%ld", (long)(i + 1));
        -: 2356:				  row_data[d_idx++] = savepv(buf);
        -: 2357:				}
        -: 2358:			}
        -: 2359:
        -: 2360:			for(size_t j=0; j<num_headers; j++) {
        -: 2361:				 SV**restrict h_ptr = av_fetch(headers_av, j, 0);
        -: 2362:				 const char *restrict col_name = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2363:				 SV **restrict cell_ptr = row_hv ? hv_fetch(row_hv, col_name, strlen(col_name), 0) : NULL;
        -: 2364:				 if (cell_ptr && SvOK(*cell_ptr)) {
        -: 2365:				     if (SvROK(*cell_ptr)) {
        -: 2366:				         PerlIO_close(fh);
        -: 2367:                         safefree(row_data);
        -: 2368:                         if (headers_av) SvREFCNT_dec(headers_av);
        -: 2369:				         croak("write_table: Cannot write nested reference types to table\n");
        -: 2370:				     }
        -: 2371:				     row_data[d_idx++] = SvPV_nolen(*cell_ptr);
        -: 2372:				 } else {
        -: 2373:				     row_data[d_idx++] = "NA";
        -: 2374:				 }
        -: 2375:			}
        -: 2376:			print_string_row(fh, row_data, d_idx, sep);
        -: 2377:			if (inc_rownames && !rownames_col) safefree((char*)row_data[0]);
        -: 2378:		}
        -: 2379:		safefree(row_data);
        -: 2380:	}
        -: 2381:	if (headers_av) SvREFCNT_dec(headers_av);
        -: 2382:	if (rows_av) SvREFCNT_dec(rows_av);
        -: 2383:	PerlIO_close(fh);
        -: 2384:	XSRETURN_EMPTY;
        -: 2385:}
        -: 2386:#line 2387 "LikeR.c"
        -: 2387:	PUTBACK;
        -: 2388:	return;
        -: 2389:    }
        -: 2390:}
        -: 2391:
        -: 2392:
        -: 2393:XS_EUPXS(XS_Stats__LikeR__parse_csv_file); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR__parse_csv_file called 39 returned 100% blocks executed 86%
       39: 2394:XS_EUPXS(XS_Stats__LikeR__parse_csv_file)
        -: 2395:{
       39: 2396:    dVAR; dXSARGS;
       39: 2396-block  0
call    0 returned 39
       39: 2397:    if (items < 3 || items > 4)
branch  0 taken 39 (fallthrough)
branch  1 taken 0
       39: 2397-block  0
branch  2 taken 0 (fallthrough)
branch  3 taken 39
    #####: 2398:       croak_xs_usage(cv,  "file, sep_str, comment_str, callback= &PL_sv_undef");
    %%%%%: 2398-block  0
call    0 never executed
        -: 2399:    {
        -: 2400:	SV *	RETVAL;
       39: 2401:	char*	file = (char *)SvPV_nolen(ST(0))
       39: 2401-block  0
call    0 returned 39
        -: 2402:;
       39: 2403:	const char*	sep_str = (const char *)SvPV_nolen(ST(1))
call    0 returned 39
        -: 2404:;
       39: 2405:	const char*	comment_str = (const char *)SvPV_nolen(ST(2))
call    0 returned 39
        -: 2406:;
        -: 2407:	SV*	callback;
        -: 2408:
       39: 2409:	if (items < 4)
branch  0 taken 0 (fallthrough)
branch  1 taken 39
    #####: 2410:	    callback = &PL_sv_undef;
    %%%%%: 2410-block  0
        -: 2411:	else {
       39: 2412:	    callback = ST(3)
       39: 2412-block  0
        -: 2413:;
        -: 2414:	}
        -: 2415:#line 2187 "LikeR.xs"
        -: 2416:	PerlIO *restrict fp;
        -: 2417:	AV *restrict data = NULL;
        -: 2418:	AV *restrict current_row = newAV();
        -: 2419:	SV *restrict field = newSVpvs("");
        -: 2420:	bool in_quotes = 0, post_quote = 0;
        -: 2421:	size_t sep_len, comment_len;
        -: 2422:	SV *restrict line_sv;
        -: 2423:	bool use_cb = 0;
        -: 2424:#line 2425 "LikeR.c"
        -: 2425:#line 2196 "LikeR.xs"
        -: 2426:	if (SvOK(callback) && SvROK(callback) && SvTYPE(SvRV(callback)) == SVt_PVCV) {
        -: 2427:		use_cb = 1;
        -: 2428:	} else {
        -: 2429:		data = newAV();
        -: 2430:	}
        -: 2431:	sep_len = sep_str ? strlen(sep_str) : 0;
        -: 2432:	comment_len = comment_str ? strlen(comment_str) : 0;
        -: 2433:
        -: 2434:	fp = PerlIO_open(file, "r");
        -: 2435:	if (!fp) {
        -: 2436:		croak("Could not open file '%s'", file);
        -: 2437:	}
        -: 2438:	line_sv = newSV_type(SVt_PV);
        -: 2439:	// Read line by line using PerlIO
        -: 2440:	while (sv_gets(line_sv, fp, 0) != NULL) {
        -: 2441:		char *restrict line = SvPV_nolen(line_sv);
        -: 2442:		size_t len = SvCUR(line_sv);
        -: 2443:		// chomp \r\n (Handles Windows invisible \r natively)
        -: 2444:		if (len > 0 && line[len-1] == '\n') {
        -: 2445:			len--;
        -: 2446:			if (len > 0 && line[len-1] == '\r') {
        -: 2447:				len--;
        -: 2448:			}
        -: 2449:		}
        -: 2450:		if (!in_quotes) {
        -: 2451:			// Skip completely empty lines (\h*[\r\n]+$ equivalent)
        -: 2452:			bool is_empty = 1;
        -: 2453:			for (size_t i = 0; i < len; i++) {
        -: 2454:				if (line[i] != ' ' && line[i] != '\t') { is_empty = 0; break; }
        -: 2455:			}
        -: 2456:			if (is_empty) continue;
        -: 2457:
        -: 2458:			// Skip comments
        -: 2459:			if (comment_len > 0 && len >= comment_len && strncmp(line, comment_str, comment_len) == 0) {
        -: 2460:				continue;
        -: 2461:			}
        -: 2462:		}
        -: 2463:		// --- CORE PARSING MACHINE ---
        -: 2464:		for (size_t i = 0; i < len; i++) {
        -: 2465:			const char ch = line[i];
        -: 2466:			if (ch == '\r') continue;
        -: 2467:			if (ch == '"') {
        -: 2468:				if (in_quotes && (i + 1 < len) && line[i+1] == '"') {
        -: 2469:					sv_catpvn(field, "\"", 1);
        -: 2470:					i++; // Skip the escaped second quote
        -: 2471:				} else if (in_quotes) {
        -: 2472:					in_quotes = 0;  // Close quotes
        -: 2473:					post_quote = 1;
        -: 2474:				} else if (!post_quote) {
        -: 2475:					in_quotes = 1; // Open quotes (only when not in post-quote state)
        -: 2476:				}
        -: 2477:			} else if (!in_quotes && sep_len > 0 && (len - i) >= sep_len && strncmp(line + i, sep_str, sep_len) == 0) {
        -: 2478:				av_push(current_row, newSVsv(field));
        -: 2479:				sv_setpvs(field, ""); // Reset for next field
        -: 2480:				i += sep_len - 1;     // Advance past multi-char separators
        -: 2481:				post_quote = 0;
        -: 2482:			} else {
        -: 2483:				sv_catpvn(field, &ch, 1);
        -: 2484:			}
        -: 2485:		}
        -: 2486:		if (in_quotes) {
        -: 2487:			// Line ended but quotes are still open! Append newline and fetch next
        -: 2488:			sv_catpvn(field, "\n", 1);
        -: 2489:		} else {
        -: 2490:			post_quote = 0; // Reset post-quote state at row boundary
        -: 2491:			// Push the final field of the record
        -: 2492:			av_push(current_row, newSVsv(field));
        -: 2493:			sv_setpvs(field, "");
        -: 2494:			// If a callback is provided, invoke it in a streaming fashion
        -: 2495:			if (use_cb) {
        -: 2496:				dSP;
        -: 2497:				ENTER;
        -: 2498:				SAVETMPS;
        -: 2499:				PUSHMARK(SP);
        -: 2500:				XPUSHs(sv_2mortal(newRV_inc((SV*)current_row)));
        -: 2501:				PUTBACK;
        -: 2502:				call_sv(callback, G_DISCARD);
        -: 2503:				FREETMPS;
        -: 2504:				LEAVE;
        -: 2505:				SvREFCNT_dec(current_row); // Frees the row from C memory if Perl didn't keep it
        -: 2506:			} else {
        -: 2507:				av_push(data, newRV_noinc((SV*)current_row));
        -: 2508:			}
        -: 2509:			current_row = newAV();
        -: 2510:		}
        -: 2511:	}
        -: 2512:	PerlIO_close(fp);
        -: 2513:	SvREFCNT_dec(line_sv);
        -: 2514:
        -: 2515:	if (in_quotes) {
        -: 2516:		av_push(current_row, newSVsv(field));
        -: 2517:		if (use_cb) {
        -: 2518:			dSP;
        -: 2519:			ENTER;
        -: 2520:			SAVETMPS;
        -: 2521:			PUSHMARK(SP);
        -: 2522:			XPUSHs(sv_2mortal(newRV_inc((SV*)current_row)));
        -: 2523:			PUTBACK;
        -: 2524:			call_sv(callback, G_DISCARD);
        -: 2525:			FREETMPS;
        -: 2526:			LEAVE;
        -: 2527:			SvREFCNT_dec(current_row);
        -: 2528:		} else {
        -: 2529:			av_push(data, newRV_noinc((SV*)current_row));
        -: 2530:		}
        -: 2531:		current_row = newAV();
        -: 2532:	}
        -: 2533:	SvREFCNT_dec(field);
        -: 2534:	SvREFCNT_dec(current_row);
        -: 2535:
        -: 2536:	if (use_cb) {
        -: 2537:		RETVAL = &PL_sv_undef; // Memory was fully handled by callback stream
        -: 2538:	} else {
        -: 2539:		RETVAL = newRV_noinc((SV*)data);
        -: 2540:	}
        -: 2541:#line 2542 "LikeR.c"
       39: 2542:	RETVAL = sv_2mortal(RETVAL);
       39: 2542-block  0
call    0 returned 39
       39: 2543:	ST(0) = RETVAL;
        -: 2544:    }
       39: 2545:    XSRETURN(1);
        -: 2546:}
        -: 2547:
        -: 2548:
        -: 2549:XS_EUPXS(XS_Stats__LikeR_cov); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_cov called 12 returned 100% blocks executed 86%
       12: 2550:XS_EUPXS(XS_Stats__LikeR_cov)
        -: 2551:{
       12: 2552:    dVAR; dXSARGS;
       12: 2552-block  0
call    0 returned 12
       12: 2553:    if (items < 2 || items > 3)
branch  0 taken 12 (fallthrough)
branch  1 taken 0
       12: 2553-block  0
branch  2 taken 0 (fallthrough)
branch  3 taken 12
    #####: 2554:       croak_xs_usage(cv,  "x_sv, y_sv, method= \"pearson\"");
    %%%%%: 2554-block  0
call    0 never executed
        -: 2555:    {
        -: 2556:	SV *	RETVAL;
       12: 2557:	SV *	x_sv = ST(0)
        -: 2558:;
       12: 2559:	SV *	y_sv = ST(1)
        -: 2560:;
        -: 2561:	const char *	method;
        -: 2562:
       12: 2563:	if (items < 3)
       12: 2563-block  0
branch  0 taken 3 (fallthrough)
branch  1 taken 9
        3: 2564:	    method = "pearson";
        3: 2564-block  0
        -: 2565:	else {
        9: 2566:	    method = (const char *)SvPV_nolen(ST(2))
        9: 2566-block  0
call    0 returned 9
        -: 2567:;
        -: 2568:	}
        -: 2569:#line 2316 "LikeR.xs"
        -: 2570:	{
        -: 2571:		// 1. Validate inputs are Array References
        -: 2572:		if (!SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV) {
        -: 2573:			croak("cov: first argument 'x' must be an ARRAY reference");
        -: 2574:		}
        -: 2575:		if (!SvROK(y_sv) || SvTYPE(SvRV(y_sv)) != SVt_PVAV) {
        -: 2576:			croak("cov: second argument 'y' must be an ARRAY reference");
        -: 2577:		}
        -: 2578:
        -: 2579:		// 2. Validate method argument
        -: 2580:		if (strcmp(method, "pearson") != 0 && 
        -: 2581:			strcmp(method, "spearman") != 0 && 
        -: 2582:			strcmp(method, "kendall") != 0) {
        -: 2583:			croak("cov: unknown method '%s' (use 'pearson', 'spearman', or 'kendall')", method);
        -: 2584:		}
        -: 2585:
        -: 2586:		AV *restrict x_av = (AV*)SvRV(x_sv);
        -: 2587:		AV *restrict y_av = (AV*)SvRV(y_sv);
        -: 2588:		size_t nx = av_len(x_av) + 1;
        -: 2589:		size_t ny = av_len(y_av) + 1;
        -: 2590:
        -: 2591:		if (nx != ny) {
        -: 2592:			croak("cov: incompatible dimensions (x has %lu, y has %lu)", 
        -: 2593:				   (unsigned long)nx, (unsigned long)ny);
        -: 2594:		}
        -: 2595:
        -: 2596:		// 3. Extract Valid Pairwise Data
        -: 2597:		// Allocate temporary C arrays for numeric processing
        -: 2598:		double *restrict x_val = (double*)safemalloc(nx * sizeof(double));
        -: 2599:		double *restrict y_val = (double*)safemalloc(nx * sizeof(double));
        -: 2600:		size_t n = 0;
        -: 2601:
        -: 2602:		for (size_t i = 0; i < nx; i++) {
        -: 2603:			SV **restrict x_tv = av_fetch(x_av, i, 0);
        -: 2604:			SV **restrict y_tv = av_fetch(y_av, i, 0);
        -: 2605:
        -: 2606:			// Extract numeric values, defaulting to NAN for missing/invalid data
        -: 2607:			double xv = (x_tv && SvOK(*x_tv) && looks_like_number(*x_tv)) ? SvNV(*x_tv) : NAN;
        -: 2608:			double yv = (y_tv && SvOK(*y_tv) && looks_like_number(*y_tv)) ? SvNV(*y_tv) : NAN;
        -: 2609:
        -: 2610:			// Pairwise complete observations (skips NAs seamlessly like R)
        -: 2611:			if (!isnan(xv) && !isnan(yv)) {
        -: 2612:				 x_val[n] = xv;
        -: 2613:				 y_val[n] = yv;
        -: 2614:				 n++;
        -: 2615:			}
        -: 2616:		}
        -: 2617:
        -: 2618:		// 4. Handle edge cases where data is too sparse
        -: 2619:		if (n < 2) {
        -: 2620:			Safefree(x_val);
        -: 2621:			Safefree(y_val);
        -: 2622:			RETVAL = newSVnv(NAN);
        -: 2623:		} else {
        -: 2624:			double ans = 0.0;
        -: 2625:
        -: 2626:			// 5. Algorithm routing
        -: 2627:			if (strcmp(method, "kendall") == 0) {
        -: 2628:				 // R's default cov(..., method="kendall") iterates the full n x n space
        -: 2629:				 for (size_t i = 0; i < n; i++) {
        -: 2630:				     for (size_t j = 0; j < n; j++) {
        -: 2631:				         int sx = (x_val[i] > x_val[j]) - (x_val[i] < x_val[j]);
        -: 2632:				         int sy = (y_val[i] > y_val[j]) - (y_val[i] < y_val[j]);
        -: 2633:				         ans += (double)(sx * sy);
        -: 2634:				     }
        -: 2635:				 }
        -: 2636:			} else {
        -: 2637:				 double mean_x = 0.0, mean_y = 0.0, cov_sum = 0.0;
        -: 2638:
        -: 2639:				 if (strcmp(method, "spearman") == 0) {
        -: 2640:				     // Spearman: Rank the data first, then run standard covariance
        -: 2641:				     double *restrict rx = (double*)safemalloc(n * sizeof(double));
        -: 2642:				     double *restrict ry = (double*)safemalloc(n * sizeof(double));
        -: 2643:
        -: 2644:				     // Uses your existing rank_data() helper from LikeR.xs
        -: 2645:				     rank_data(x_val, rx, n);
        -: 2646:				     rank_data(y_val, ry, n);
        -: 2647:
        -: 2648:				     for (size_t i = 0; i < n; i++) {
        -: 2649:				         double dx = rx[i] - mean_x;
        -: 2650:				         mean_x += dx / (i + 1);
        -: 2651:				         double dy = ry[i] - mean_y;
        -: 2652:				         mean_y += dy / (i + 1);
        -: 2653:				         cov_sum += dx * (ry[i] - mean_y);
        -: 2654:				     }
        -: 2655:
        -: 2656:				     Safefree(rx);
        -: 2657:				     Safefree(ry);
        -: 2658:				 } else { 
        -: 2659:				     // Pearson: Welford's Single-Pass Covariance Algorithm
        -: 2660:				     for (size_t i = 0; i < n; i++) {
        -: 2661:				         double dx = x_val[i] - mean_x;
        -: 2662:				         mean_x += dx / (i + 1);
        -: 2663:				         double dy = y_val[i] - mean_y;
        -: 2664:				         mean_y += dy / (i + 1);
        -: 2665:				         cov_sum += dx * (y_val[i] - mean_y);
        -: 2666:				     }
        -: 2667:				 }
        -: 2668:
        -: 2669:				 // Unbiased Sample Covariance (N - 1) for Pearson & Spearman
        -: 2670:				 ans = cov_sum / (n - 1);
        -: 2671:			}
        -: 2672:
        -: 2673:			Safefree(x_val);
        -: 2674:			Safefree(y_val);
        -: 2675:			RETVAL = newSVnv(ans);
        -: 2676:		}
        -: 2677:	}
        -: 2678:#line 2679 "LikeR.c"
       12: 2679:	RETVAL = sv_2mortal(RETVAL);
       12: 2679-block  0
call    0 returned 12
       12: 2680:	ST(0) = RETVAL;
        -: 2681:    }
       12: 2682:    XSRETURN(1);
        -: 2683:}
        -: 2684:
        -: 2685:
        -: 2686:XS_EUPXS(XS_Stats__LikeR_glm); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_glm called 21 returned 100% blocks executed 79%
       21: 2687:XS_EUPXS(XS_Stats__LikeR_glm)
        -: 2688:{
       21: 2689:    dVAR; dXSARGS;
       21: 2689-block  0
call    0 returned 21
branch  1 taken 0 (fallthrough)
branch  2 taken 21
        -: 2690:    PERL_UNUSED_VAR(cv); /* -W */
        -: 2691:    PERL_UNUSED_VAR(items); /* -W */
        -: 2692:    {
        -: 2693:	SV *	RETVAL;
        -: 2694:#line 2429 "LikeR.xs"
        -: 2695:{
        -: 2696:	const char *restrict formula  = NULL;
        -: 2697:	SV *restrict data_sv = NULL;
        -: 2698:	const char *restrict family_str = "gaussian";
        -: 2699:	char f_cpy[512];
        -: 2700:	char *restrict src, *restrict dst, *restrict tilde, *restrict lhs, *restrict rhs, *restrict chunk;
        -: 2701:
        -: 2702:	/* Dynamic Term Arrays */
        -: 2703:	char **restrict terms = NULL, **restrict uniq_terms = NULL, **restrict exp_terms = NULL;
        -: 2704:	bool *restrict is_dummy = NULL;
        -: 2705:	char **restrict dummy_base = NULL, **restrict dummy_level = NULL;
        -: 2706:	unsigned int term_cap = 64, exp_cap = 64, num_terms = 0, num_uniq = 0, p = 0, p_exp = 0;
        -: 2707:	size_t n = 0, valid_n = 0, i;
        -: 2708:	bool has_intercept = true, converged = false, boundary = false;
        -: 2709:	int iter = 0, max_iter = 25, final_rank = 0, df_res = 0;
        -: 2710:	double deviance_old = 0.0, deviance_new = 0.0, null_dev = 0.0, aic = 0.0;
        -: 2711:	double dispersion = 0.0, epsilon = 1e-8;
        -: 2712:
        -: 2713:	char **restrict row_names = NULL;
        -: 2714:	char **restrict valid_row_names = NULL;
        -: 2715:	HV **restrict row_hashes = NULL;
        -: 2716:	HV *restrict data_hoa = NULL;
        -: 2717:	SV *restrict ref = NULL;
        -: 2718:
        -: 2719:	double *restrict X = NULL, *restrict Y = NULL, *restrict mu = NULL, *restrict eta = NULL;
        -: 2720:	double *restrict W = NULL, *restrict Z = NULL, *restrict beta = NULL, *restrict beta_old = NULL;
        -: 2721:	bool *restrict aliased = NULL;
        -: 2722:	double *restrict XtWX = NULL, *restrict XtWZ = NULL;
        -: 2723:
        -: 2724:	HV *restrict res_hv, *restrict coef_hv, *restrict fitted_hv, *restrict resid_hv, *restrict summary_hv;
        -: 2725:	AV *restrict terms_av;
        -: 2726:	HE *restrict entry;
        -: 2727:
        -: 2728:	if (items % 2 != 0) croak("Usage: glm(formula => 'am ~ wt + hp', data => \\%mtcars)");
        -: 2729:
        -: 2730:	for (unsigned short i_arg = 0; i_arg < items; i_arg += 2) {
        -: 2731:	  const char *restrict key = SvPV_nolen(ST(i_arg));
        -: 2732:	  SV *restrict val = ST(i_arg + 1);
        -: 2733:	  if      (strEQ(key, "formula")) formula = SvPV_nolen(val);
        -: 2734:	  else if (strEQ(key, "data"))    data_sv = val;
        -: 2735:	  else if (strEQ(key, "family"))  family_str = SvPV_nolen(val);
        -: 2736:	  else croak("glm: unknown argument '%s'", key);
        -: 2737:	}        
        -: 2738:	if (!formula) croak("glm: formula is required");
        -: 2739:	if (!data_sv || !SvROK(data_sv)) croak("glm: data is required and must be a reference");
        -: 2740:
        -: 2741:	bool is_binomial = (strcmp(family_str, "binomial") == 0);
        -: 2742:	bool is_gaussian = (strcmp(family_str, "gaussian") == 0);
        -: 2743:	if (!is_binomial && !is_gaussian) croak("glm: unsupported family '%s'", family_str);
        -: 2744:
        -: 2745:	// --- Formula Parsing & Expansion ---
        -: 2746:	Newx(terms, term_cap, char*); Newx(uniq_terms, term_cap, char*);
        -: 2747:	Newx(exp_terms, exp_cap, char*); Newx(is_dummy, exp_cap, bool);
        -: 2748:	Newx(dummy_base, exp_cap, char*); Newx(dummy_level, exp_cap, char*);
        -: 2749:
        -: 2750:	src = (char*)formula; dst = f_cpy;
        -: 2751:	while (*src && (dst - f_cpy < 511)) { if (!isspace(*src)) { *dst++ = *src; } src++; }
        -: 2752:	*dst = '\0';
        -: 2753:
        -: 2754:	tilde = strchr(f_cpy, '~');
        -: 2755:	if (!tilde) croak("glm: invalid formula, missing '~'");
        -: 2756:	*tilde = '\0';
        -: 2757:	lhs = f_cpy; rhs = tilde + 1;
        -: 2758:
        -: 2759:	if (strstr(rhs, "-1")) has_intercept = false;
        -: 2760:	if (has_intercept) terms[num_terms++] = savepv("Intercept");
        -: 2761:
        -: 2762:	chunk = strtok(rhs, "+");
        -: 2763:	while (chunk != NULL) {
        -: 2764:	  if (num_terms >= term_cap - 3) {
        -: 2765:		   term_cap *= 2;
        -: 2766:		   Renew(terms, term_cap, char*); Renew(uniq_terms, term_cap, char*);
        -: 2767:	  }
        -: 2768:	  if (strcmp(chunk, "1") == 0 || strcmp(chunk, "-1") == 0) {
        -: 2769:		   chunk = strtok(NULL, "+");
        -: 2770:		   continue;
        -: 2771:	  }
        -: 2772:	  char *restrict star = strchr(chunk, '*');
        -: 2773:	  if (star) {
        -: 2774:		   *star = '\0';
        -: 2775:		   char *restrict left = chunk; char *restrict right = star + 1;
        -: 2776:		   char *restrict c_l = strchr(left, '^'); if (c_l && strncmp(left, "I(", 2) != 0) *c_l = '\0';
        -: 2777:		   char *restrict c_r = strchr(right, '^'); if (c_r && strncmp(right, "I(", 2) != 0) *c_r = '\0';
        -: 2778:
        -: 2779:		   terms[num_terms++] = savepv(left);
        -: 2780:		   terms[num_terms++] = savepv(right);
        -: 2781:		   size_t inter_len = strlen(left) + strlen(right) + 2;
        -: 2782:		   terms[num_terms] = (char*)safemalloc(inter_len);
        -: 2783:		   snprintf(terms[num_terms++], inter_len, "%s:%s", left, right);
        -: 2784:	  } else {
        -: 2785:		   char *restrict c_chunk = strchr(chunk, '^'); 
        -: 2786:		   if (c_chunk && strncmp(chunk, "I(", 2) != 0) *c_chunk = '\0';
        -: 2787:		   terms[num_terms++] = savepv(chunk);
        -: 2788:	  }
        -: 2789:	  chunk = strtok(NULL, "+");
        -: 2790:	}
        -: 2791:
        -: 2792:	for (i = 0; i < num_terms; i++) {
        -: 2793:	  bool found = false;
        -: 2794:	  for (size_t j = 0; j < num_uniq; j++) {
        -: 2795:		   if (strcmp(terms[i], uniq_terms[j]) == 0) { found = true; break; }
        -: 2796:	  }
        -: 2797:	  if (!found) uniq_terms[num_uniq++] = savepv(terms[i]);
        -: 2798:	}
        -: 2799:	p = num_uniq;
        -: 2800:
        -: 2801:	// --- Data Extraction ---
        -: 2802:	ref = SvRV(data_sv);
        -: 2803:	if (SvTYPE(ref) == SVt_PVHV) {
        -: 2804:		HV*restrict hv = (HV*)ref;
        -: 2805:		if (hv_iterinit(hv) == 0) croak("glm: Data hash is empty");
        -: 2806:		entry = hv_iternext(hv);
        -: 2807:		if (entry) {
        -: 2808:			SV*restrict val = hv_iterval(hv, entry);
        -: 2809:			if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
        -: 2810:				 data_hoa = hv;
        -: 2811:				 n = av_len((AV*)SvRV(val)) + 1;
        -: 2812:				 Newx(row_names, n, char*);
        -: 2813:				 for(i = 0; i < n; i++) {
        -: 2814:				     char buf[32]; snprintf(buf, sizeof(buf), "%lu", i+1);
        -: 2815:				     row_names[i] = savepv(buf);
        -: 2816:				 }
        -: 2817:			} else if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVHV) {
        -: 2818:				 n = hv_iterinit(hv);
        -: 2819:				 Newx(row_names, n, char*); Newx(row_hashes, n, HV*);
        -: 2820:				 i = 0;
        -: 2821:				 while ((entry = hv_iternext(hv))) {
        -: 2822:				     unsigned int len;
        -: 2823:				     row_names[i] = savepv(hv_iterkey(entry, &len));
        -: 2824:				     row_hashes[i] = (HV*)SvRV(hv_iterval(hv, entry));
        -: 2825:				     i++;
        -: 2826:				 }
        -: 2827:			} else croak("glm: Hash values must be ArrayRefs (HoA) or HashRefs (HoH)");
        -: 2828:		}
        -: 2829:	} else if (SvTYPE(ref) == SVt_PVAV) {
        -: 2830:	  AV*restrict av = (AV*)ref;
        -: 2831:	  n = av_len(av) + 1;
        -: 2832:	  Newx(row_names, n, char*); Newx(row_hashes, n, HV*);
        -: 2833:	  for (i = 0; i < n; i++) {
        -: 2834:		   SV**restrict val = av_fetch(av, i, 0);
        -: 2835:		   if (val && SvROK(*val) && SvTYPE(SvRV(*val)) == SVt_PVHV) {
        -: 2836:		       row_hashes[i] = (HV*)SvRV(*val);
        -: 2837:		       char buf[32]; snprintf(buf, sizeof(buf), "%lu", i + 1);
        -: 2838:		       row_names[i] = savepv(buf);
        -: 2839:		   } else {
        -: 2840:		       for (size_t k = 0; k < i; k++) Safefree(row_names[k]);
        -: 2841:		       Safefree(row_names); Safefree(row_hashes);
        -: 2842:		       croak("glm: Array values must be HashRefs (AoH)");
        -: 2843:		   }
        -: 2844:	  }
        -: 2845:	} else croak("glm: Data must be an Array or Hash reference");
        -: 2846:
        -: 2847:	// --- Categorical Expansion ---
        -: 2848:	for (size_t j = 0; j < p; j++) {
        -: 2849:	  if (p_exp + 32 >= exp_cap) {
        -: 2850:		   exp_cap *= 2;
        -: 2851:		   Renew(exp_terms, exp_cap, char*); Renew(is_dummy, exp_cap, bool);
        -: 2852:		   Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 2853:	  }
        -: 2854:	  if (strcmp(uniq_terms[j], "Intercept") == 0) {
        -: 2855:		   exp_terms[p_exp] = savepv("Intercept"); is_dummy[p_exp] = false; p_exp++; continue;
        -: 2856:	  }
        -: 2857:	  if (is_column_categorical(data_hoa, row_hashes, n, uniq_terms[j])) {
        -: 2858:		   char **restrict levels = NULL; size_t num_levels = 0, levels_cap = 8;
        -: 2859:		   Newx(levels, levels_cap, char*);
        -: 2860:		   for (i = 0; i < n; i++) {
        -: 2861:		       char*restrict str_val = get_data_string_alloc(data_hoa, row_hashes, i, uniq_terms[j]);
        -: 2862:		       if (str_val) {
        -: 2863:		           bool found = false;
        -: 2864:		           for (size_t l = 0; l < num_levels; l++) {
        -: 2865:		               if (strcmp(levels[l], str_val) == 0) { found = true; break; }
        -: 2866:		           }
        -: 2867:		           if (!found) {
        -: 2868:		               if (num_levels >= levels_cap) { levels_cap *= 2; Renew(levels, levels_cap, char*); }
        -: 2869:		               levels[num_levels++] = savepv(str_val);
        -: 2870:		           }
        -: 2871:		           Safefree(str_val);
        -: 2872:		       }
        -: 2873:		   }
        -: 2874:		   if (num_levels > 0) {
        -: 2875:		       for (size_t l1 = 0; l1 < num_levels - 1; l1++) {
        -: 2876:		           for (size_t l2 = l1 + 1; l2 < num_levels; l2++) {
        -: 2877:		               if (strcmp(levels[l1], levels[l2]) > 0) {
        -: 2878:		                   char *tmp = levels[l1]; levels[l1] = levels[l2]; levels[l2] = tmp;
        -: 2879:		               }
        -: 2880:		           }
        -: 2881:		       }
        -: 2882:		       for (size_t l = 1; l < num_levels; l++) {
        -: 2883:		           if (p_exp >= exp_cap) {
        -: 2884:		               exp_cap *= 2;
        -: 2885:		               Renew(exp_terms, exp_cap, char*); Renew(is_dummy, exp_cap, bool);
        -: 2886:		               Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 2887:		           }
        -: 2888:		           size_t t_len = strlen(uniq_terms[j]) + strlen(levels[l]) + 1;
        -: 2889:		           exp_terms[p_exp] = (char*)safemalloc(t_len);
        -: 2890:		           snprintf(exp_terms[p_exp], t_len, "%s%s", uniq_terms[j], levels[l]);
        -: 2891:		           is_dummy[p_exp] = true; dummy_base[p_exp] = savepv(uniq_terms[j]); dummy_level[p_exp] = savepv(levels[l]);
        -: 2892:		           p_exp++;
        -: 2893:		       }
        -: 2894:		       for (size_t l = 0; l < num_levels; l++) Safefree(levels[l]);
        -: 2895:		       Safefree(levels);
        -: 2896:		   } else {
        -: 2897:		       Safefree(levels); exp_terms[p_exp] = savepv(uniq_terms[j]); is_dummy[p_exp] = false; p_exp++;
        -: 2898:		   }
        -: 2899:	  } else {
        -: 2900:		   exp_terms[p_exp] = savepv(uniq_terms[j]); is_dummy[p_exp] = false; p_exp++;
        -: 2901:	  }
        -: 2902:	}
        -: 2903:	p = p_exp;
        -: 2904:
        -: 2905:	Newx(X, n * p, double); Newx(Y, n, double);
        -: 2906:	Newx(valid_row_names, n, char*);
        -: 2907:
        -: 2908:	// --- Listwise Deletion ---
        -: 2909:	for (size_t i = 0; i < n; i++) {
        -: 2910:		double y_val = evaluate_term(data_hoa, row_hashes, i, lhs);
        -: 2911:		if (isnan(y_val)) { Safefree(row_names[i]); continue; }
        -: 2912:
        -: 2913:		bool row_ok = true;
        -: 2914:		double *restrict row_x = (double*)safemalloc(p * sizeof(double));
        -: 2915:		for (size_t j = 0; j < p; j++) {
        -: 2916:			if (strcmp(exp_terms[j], "Intercept") == 0) {
        -: 2917:				 row_x[j] = 1.0;
        -: 2918:			} else if (is_dummy[j]) {
        -: 2919:				 char* str_val = get_data_string_alloc(data_hoa, row_hashes, i, dummy_base[j]);
        -: 2920:				 if (str_val) {
        -: 2921:				     row_x[j] = (strcmp(str_val, dummy_level[j]) == 0) ? 1.0 : 0.0;
        -: 2922:				     Safefree(str_val);
        -: 2923:				 } else { row_ok = false; break; }
        -: 2924:			} else {
        -: 2925:				 row_x[j] = evaluate_term(data_hoa, row_hashes, i, exp_terms[j]);
        -: 2926:				 if (isnan(row_x[j])) { row_ok = false; break; }
        -: 2927:			}
        -: 2928:		}
        -: 2929:		if (!row_ok) { Safefree(row_names[i]); Safefree(row_x); continue; }
        -: 2930:		Y[valid_n] = y_val;
        -: 2931:		for (size_t j = 0; j < p; j++) X[valid_n * p + j] = row_x[j];
        -: 2932:		valid_row_names[valid_n] = row_names[i];
        -: 2933:		valid_n++;
        -: 2934:		Safefree(row_x);
        -: 2935:	}
        -: 2936:	Safefree(row_names); 
        -: 2937:	if (valid_n <= p) {
        -: 2938:	  Safefree(X); Safefree(Y); Safefree(valid_row_names); if (row_hashes) Safefree(row_hashes);
        -: 2939:	  croak("glm: 0 degrees of freedom (too many NAs or parameters > observations)");
        -: 2940:	}
        -: 2941:	// --- R glm.fit IRLS Implementation ---
        -: 2942:	mu = (double*)safemalloc(valid_n * sizeof(double)); eta = (double*)safemalloc(valid_n * sizeof(double));
        -: 2943:	W = (double*)safemalloc(valid_n * sizeof(double)); Z = (double*)safemalloc(valid_n * sizeof(double));
        -: 2944:	beta = (double*)safemalloc(p * sizeof(double)); beta_old = (double*)safemalloc(p * sizeof(double));
        -: 2945:	aliased = (bool*)safemalloc(p * sizeof(bool));
        -: 2946:	XtWX = (double*)safemalloc(p * p * sizeof(double)); XtWZ = (double*)safemalloc(p * sizeof(double));
        -: 2947:	for (i = 0; i < p; i++) { beta[i] = 0.0; beta_old[i] = 0.0; }
        -: 2948:	// Initialize (mustart / etastart equivalent)
        -: 2949:	double sum_y = 0.0;
        -: 2950:	for (i = 0; i < valid_n; i++) sum_y += Y[i];
        -: 2951:	double mean_y = sum_y / valid_n;
        -: 2952:	for (i = 0; i < valid_n; i++) {
        -: 2953:	  if (is_binomial) {
        -: 2954:		   if (Y[i] < 0.0 || Y[i] > 1.0) croak("glm: binomial family requires response between 0 and 1");
        -: 2955:		   mu[i] = (Y[i] + 0.5) / 2.0; 
        -: 2956:		   eta[i] = log(mu[i] / (1.0 - mu[i]));
        -: 2957:		   double dev = 0.0;
        -: 2958:		   if (Y[i] == 0.0)      dev = -2.0 * log(1.0 - mu[i]);
        -: 2959:		   else if (Y[i] == 1.0) dev = -2.0 * log(mu[i]);
        -: 2960:		   else dev = 2.0 * (Y[i] * log(Y[i] / mu[i]) + (1.0 - Y[i]) * log((1.0 - Y[i]) / (1.0 - mu[i])));
        -: 2961:		   deviance_old += dev;
        -: 2962:	  } else { 
        -: 2963:		   mu[i] = mean_y; // R gaussian init
        -: 2964:		   eta[i] = mu[i]; 
        -: 2965:	  }
        -: 2966:	}
        -: 2967:	// IRLS Loop
        -: 2968:	for (iter = 1; iter <= max_iter; iter++) {
        -: 2969:		for (i = 0; i < valid_n; i++) {
        -: 2970:			if (is_binomial) {
        -: 2971:				 double varmu = mu[i] * (1.0 - mu[i]);
        -: 2972:				 double mu_eta = varmu; // Link derivative for logit
        -: 2973:				 if (varmu < 1e-10) varmu = 1e-10;
        -: 2974:				 Z[i] = eta[i] + (Y[i] - mu[i]) / mu_eta;
        -: 2975:				 W[i] = (mu_eta * mu_eta) / varmu; 
        -: 2976:			} else { 
        -: 2977:				 W[i] = 1.0; 
        -: 2978:				 Z[i] = Y[i]; 
        -: 2979:			}
        -: 2980:		}
        -: 2981:		// Formulate XtWX and XtWZ
        -: 2982:		for (i = 0; i < p; i++) { XtWZ[i] = 0.0; for (size_t j = 0; j < p; j++) XtWX[i * p + j] = 0.0; }
        -: 2983:		for (size_t k = 0; k < valid_n; k++) {
        -: 2984:			double w = W[k], z = Z[k];
        -: 2985:			for (i = 0; i < p; i++) {
        -: 2986:				 XtWZ[i] += X[k * p + i] * w * z;
        -: 2987:				 double xw = X[k * p + i] * w;
        -: 2988:				 for (size_t j = 0; j < p; j++) XtWX[i * p + j] += xw * X[k * p + j];
        -: 2989:			}
        -: 2990:		}
        -: 2991:		final_rank = sweep_matrix_ols(XtWX, p, aliased);
        -: 2992:		for (i = 0; i < p; i++) {
        -: 2993:			if (aliased[i]) { beta[i] = NAN; } else {
        -: 2994:				 double sum = 0.0;
        -: 2995:				 for (size_t j = 0; j < p; j++) if (!aliased[j]) sum += XtWX[i * p + j] * XtWZ[j];
        -: 2996:				 beta[i] = sum;
        -: 2997:			}
        -: 2998:		}
        -: 2999:		// Calculate updated ETA, MU, and Deviance (with Step-Halving)
        -: 3000:		boundary = false;
        -: 3001:		for (unsigned short int half = 0; half < 10; half++) {
        -: 3002:			deviance_new = 0.0;
        -: 3003:			for (i = 0; i < valid_n; i++) {
        -: 3004:				 double linear_pred = 0.0;
        -: 3005:				 for (size_t j = 0; j < p; j++) if (!aliased[j]) linear_pred += X[i * p + j] * beta[j];
        -: 3006:				 eta[i] = linear_pred;
        -: 3007:				 if (is_binomial) {
        -: 3008:				     mu[i] = 1.0 / (1.0 + exp(-eta[i]));
        -: 3009:				     // Boundary enforcement
        -: 3010:				     if (mu[i] < 10 * DBL_EPSILON) mu[i] = 10 * DBL_EPSILON;
        -: 3011:				     if (mu[i] > 1.0 - 10 * DBL_EPSILON) mu[i] = 1.0 - 10 * DBL_EPSILON;
        -: 3012:
        -: 3013:				     double dev = 0.0;
        -: 3014:				     if (Y[i] == 0.0)      dev = -2.0 * log(1.0 - mu[i]);
        -: 3015:				     else if (Y[i] == 1.0) dev = -2.0 * log(mu[i]);
        -: 3016:				     else dev = 2.0 * (Y[i] * log(Y[i] / mu[i]) + (1.0 - Y[i]) * log((1.0 - Y[i]) / (1.0 - mu[i])));
        -: 3017:				     deviance_new += dev;
        -: 3018:				 } else {
        -: 3019:				     mu[i] = eta[i];
        -: 3020:				     double res = Y[i] - mu[i];
        -: 3021:				     deviance_new += res * res;
        -: 3022:				 }
        -: 3023:			}
        -: 3024:			// Step halving divergence check
        -: 3025:			if (!is_binomial || deviance_new <= deviance_old + 1e-7 || !isfinite(deviance_new)) {
        -: 3026:				 continue; 
        -: 3027:			}
        -: 3028:
        -: 3029:			boundary = true;
        -: 3030:			for (size_t j = 0; j < p; j++) beta[j] = (beta[j] + beta_old[j]) / 2.0;
        -: 3031:		}
        -: 3032:		// Convergence Check
        -: 3033:		if (fabs(deviance_new - deviance_old) / (0.1 + fabs(deviance_new)) < epsilon) { 
        -: 3034:			converged = true; break; 
        -: 3035:		}
        -: 3036:		deviance_old = deviance_new;
        -: 3037:		for (size_t j = 0; j < p; j++) beta_old[j] = beta[j];
        -: 3038:	}
        -: 3039:	// Final accurate calculation of W for standard errors
        -: 3040:	for (i = 0; i < p; i++) { for (size_t j = 0; j < p; j++) XtWX[i * p + j] = 0.0; }
        -: 3041:	for (size_t k = 0; k < valid_n; k++) {
        -: 3042:	  double w = is_binomial ? (mu[k] * (1.0 - mu[k])) : 1.0;
        -: 3043:	  if (w < 1e-10) w = 1e-10;
        -: 3044:	  for (i = 0; i < p; i++) {
        -: 3045:		   double xw = X[k * p + i] * w;
        -: 3046:		   for (size_t j = 0; j < p; j++) XtWX[i * p + j] += xw * X[k * p + j];
        -: 3047:	  }
        -: 3048:	}
        -: 3049:	final_rank = sweep_matrix_ols(XtWX, p, aliased);
        -: 3050:	// --- Null Deviance Calculation ---
        -: 3051:	double wtdmu = mean_y; // Since weights are 1.0 initially
        -: 3052:	for (i = 0; i < valid_n; i++) {
        -: 3053:	  if (is_binomial) {
        -: 3054:		   if (Y[i] == 0.0)      null_dev += -2.0 * log(1.0 - wtdmu);
        -: 3055:		   else if (Y[i] == 1.0) null_dev += -2.0 * log(wtdmu);
        -: 3056:		   else null_dev += 2.0 * (Y[i] * log(Y[i] / wtdmu) + (1.0 - Y[i]) * log((1.0 - Y[i]) / (1.0 - wtdmu)));
        -: 3057:	  } else {
        -: 3058:		   double diff = Y[i] - wtdmu;
        -: 3059:		   null_dev += diff * diff;
        -: 3060:	  }
        -: 3061:	}
        -: 3062:	// --- AIC Calculation ---
        -: 3063:	if (is_gaussian) {
        -: 3064:	  double n_f = (double)valid_n;
        -: 3065:	  aic = n_f * (log(2.0 * M_PI) + 1.0 + log(deviance_new / n_f)) + 2.0 * (final_rank + 1.0);
        -: 3066:	} else if (is_binomial) { 
        -: 3067:	  aic = deviance_new + 2.0 * final_rank; 
        -: 3068:	}
        -: 3069:	// --- Return Structures ---
        -: 3070:	res_hv = newHV(); coef_hv = newHV(); fitted_hv = newHV(); resid_hv = newHV();
        -: 3071:	df_res = valid_n - final_rank;
        -: 3072:	dispersion = is_binomial ? 1.0 : ((df_res > 0) ? (deviance_new / df_res) : NAN);
        -: 3073:	for (size_t i = 0; i < valid_n; i++) {
        -: 3074:		double res = Y[i] - mu[i];
        -: 3075:		if (is_binomial) {
        -: 3076:			// Deviance residuals for binomial
        -: 3077:			double d_res = 0.0;
        -: 3078:			if (Y[i] == 0.0)      d_res = sqrt(-2.0 * log(1.0 - mu[i]));
        -: 3079:			else if (Y[i] == 1.0) d_res = sqrt(-2.0 * log(mu[i]));
        -: 3080:			else d_res = sqrt(2.0 * (Y[i] * log(Y[i] / mu[i]) + (1.0 - Y[i]) * log((1.0 - Y[i]) / (1.0 - mu[i]))));
        -: 3081:			res = (Y[i] > mu[i]) ? d_res : -d_res;
        -: 3082:		}
        -: 3083:		hv_store(fitted_hv, valid_row_names[i], strlen(valid_row_names[i]), newSVnv(mu[i]), 0);
        -: 3084:		hv_store(resid_hv,  valid_row_names[i], strlen(valid_row_names[i]), newSVnv(res), 0);
        -: 3085:		Safefree(valid_row_names[i]);
        -: 3086:	}
        -: 3087:	Safefree(valid_row_names);
        -: 3088:
        -: 3089:	summary_hv = newHV(); terms_av = newAV();
        -: 3090:	for (size_t j = 0; j < p; j++) {
        -: 3091:		hv_store(coef_hv, exp_terms[j], strlen(exp_terms[j]), newSVnv(beta[j]), 0);
        -: 3092:		av_push(terms_av, newSVpv(exp_terms[j], 0));
        -: 3093:
        -: 3094:		HV *restrict row_hv = newHV();
        -: 3095:		if (aliased[j]) {
        -: 3096:			hv_store(row_hv, "Estimate",   8, newSVpv("NaN", 0), 0);
        -: 3097:			hv_store(row_hv, "Std. Error", 10, newSVpv("NaN", 0), 0);
        -: 3098:			hv_store(row_hv, is_binomial ? "z value" : "t value", 7, newSVpv("NaN", 0), 0);
        -: 3099:			hv_store(row_hv, is_binomial ? "Pr(>|z|)" : "Pr(>|t|)", 8, newSVpv("NaN", 0), 0);
        -: 3100:		} else {
        -: 3101:			double se = sqrt(dispersion * XtWX[j * p + j]);
        -: 3102:			double val_stat = beta[j] / se;
        -: 3103:			double p_val = is_binomial ? 2.0 * (1.0 - approx_pnorm(fabs(val_stat))) : get_t_pvalue(val_stat, df_res, "two.sided");
        -: 3104:
        -: 3105:			hv_store(row_hv, "Estimate",   8, newSVnv(beta[j]), 0);
        -: 3106:			hv_store(row_hv, "Std. Error", 10, newSVnv(se), 0);
        -: 3107:			hv_store(row_hv, is_binomial ? "z value" : "t value", 7, newSVnv(val_stat), 0);
        -: 3108:			hv_store(row_hv, is_binomial ? "Pr(>|z|)" : "Pr(>|t|)", 8, newSVnv(p_val), 0);
        -: 3109:		}
        -: 3110:		hv_store(summary_hv, exp_terms[j], strlen(exp_terms[j]), newRV_noinc((SV*)row_hv), 0);
        -: 3111:	}
        -: 3112:
        -: 3113:	hv_store(res_hv, "aic",            3, newSVnv(aic), 0);
        -: 3114:	hv_store(res_hv, "coefficients",  12, newRV_noinc((SV*)coef_hv), 0);
        -: 3115:	hv_store(res_hv, "converged",      9, newSVuv(converged ? 1 : 0), 0);
        -: 3116:	hv_store(res_hv, "boundary",       8, newSVuv(boundary ? 1 : 0), 0);
        -: 3117:	hv_store(res_hv, "deviance",       8, newSVnv(deviance_new), 0);
        -: 3118:	hv_store(res_hv, "deviance.resid", 14, newRV_noinc((SV*)resid_hv), 0);
        -: 3119:	hv_store(res_hv, "df.null",        7, newSVuv(valid_n - has_intercept), 0);
        -: 3120:	hv_store(res_hv, "df.residual",   11, newSVuv(df_res), 0);
        -: 3121:	hv_store(res_hv, "family",         6, newSVpv(family_str, 0), 0);
        -: 3122:	hv_store(res_hv, "fitted.values", 13, newRV_noinc((SV*)fitted_hv), 0);
        -: 3123:	hv_store(res_hv, "iter",           4, newSVuv(iter > max_iter ? max_iter : iter), 0);
        -: 3124:	hv_store(res_hv, "null.deviance", 13, newSVnv(null_dev), 0);
        -: 3125:	hv_store(res_hv, "rank",           4, newSVuv(final_rank), 0);
        -: 3126:	hv_store(res_hv, "summary",        7, newRV_noinc((SV*)summary_hv), 0);
        -: 3127:	hv_store(res_hv, "terms",          5, newRV_noinc((SV*)terms_av), 0);
        -: 3128:
        -: 3129:	// --- Cleanup ---
        -: 3130:	for (i = 0; i < num_terms; i++) Safefree(terms[i]);
        -: 3131:	Safefree(terms);
        -: 3132:	for (i = 0; i < num_uniq; i++) Safefree(uniq_terms[i]);
        -: 3133:	Safefree(uniq_terms);
        -: 3134:	for (size_t j = 0; j < p_exp; j++) {
        -: 3135:		Safefree(exp_terms[j]);
        -: 3136:		if (is_dummy[j]) { Safefree(dummy_base[j]); Safefree(dummy_level[j]); }
        -: 3137:	}
        -: 3138:	Safefree(exp_terms); Safefree(is_dummy); Safefree(dummy_base); Safefree(dummy_level);
        -: 3139:
        -: 3140:	Safefree(mu); Safefree(eta); Safefree(Z); Safefree(W);
        -: 3141:	Safefree(beta); Safefree(beta_old); Safefree(aliased);
        -: 3142:	Safefree(XtWX); Safefree(XtWZ); Safefree(X); Safefree(Y);
        -: 3143:	if (row_hashes) Safefree(row_hashes);
        -: 3144:
        -: 3145:	RETVAL = newRV_noinc((SV*)res_hv);
        -: 3146:}
        -: 3147:#line 3148 "LikeR.c"
       21: 3148:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 21
       21: 3149:	ST(0) = RETVAL;
        -: 3150:    }
       21: 3151:    XSRETURN(1);
        -: 3152:}
        -: 3153:
        -: 3154:
        -: 3155:XS_EUPXS(XS_Stats__LikeR_cor_test); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_cor_test called 21 returned 100% blocks executed 76%
       21: 3156:XS_EUPXS(XS_Stats__LikeR_cor_test)
        -: 3157:{
       21: 3158:    dVAR; dXSARGS;
       21: 3158-block  0
call    0 returned 21
branch  1 taken 21 (fallthrough)
branch  2 taken 0
        -: 3159:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3160:    PERL_UNUSED_VAR(items); /* -W */
        -: 3161:    {
        -: 3162:	SV *	RETVAL;
        -: 3163:#line 2886 "LikeR.xs"
        -: 3164:{
        -: 3165:	if (items < 2 || items % 2 != 0)
        -: 3166:		croak("Usage: cor_test(\\@x, \\@y, method => 'pearson', ...)");
        -: 3167:
        -: 3168:	SV *restrict x_ref = ST(0), *restrict y_ref = ST(1);
        -: 3169:
        -: 3170:	const char *restrict alternative = "two.sided";
        -: 3171:	const char *restrict method = "pearson";
        -: 3172:	SV *restrict exact_sv = NULL;
        -: 3173:	double conf_level = 0.95;
        -: 3174:	bool continuity = 0;
        -: 3175:
        -: 3176:	/* Parse named arguments from the flat stack starting at index 2 */
        -: 3177:	for (unsigned short int i = 2; i < items; i += 2) {
        -: 3178:	  const char *restrict key = SvPV_nolen(ST(i));
        -: 3179:	  SV *restrict val = ST(i + 1);
        -: 3180:
        -: 3181:	  if      (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 3182:	  else if (strEQ(key, "method"))      method = SvPV_nolen(val);
        -: 3183:	  else if (strEQ(key, "exact"))       exact_sv = val;
        -: 3184:	  else if (strEQ(key, "conf.level") || strEQ(key, "conf_level")) conf_level = SvNV(val);
        -: 3185:	  else if (strEQ(key, "continuity"))  continuity = SvTRUE(val);
        -: 3186:	  else croak("cor_test: unknown argument '%s'", key);
        -: 3187:	}
        -: 3188:
        -: 3189:	AV *restrict x_av, *restrict y_av;
        -: 3190:	double *restrict x, *restrict y;
        -: 3191:	double estimate = 0, p_value = 0, statistic = 0, df = 0, ci_lower = 0, ci_upper = 0;
        -: 3192:
        -: 3193:	bool is_pearson  = (strcmp(method, "pearson") == 0);
        -: 3194:	bool is_kendall  = (strcmp(method, "kendall") == 0);
        -: 3195:	bool is_spearman = (strcmp(method, "spearman") == 0);
        -: 3196:	HV *restrict rhv;
        -: 3197:
        -: 3198:	if (!SvOK(x_ref) || !SvROK(x_ref) || SvTYPE(SvRV(x_ref)) != SVt_PVAV ||
        -: 3199:	    !SvOK(y_ref) || !SvROK(y_ref) || SvTYPE(SvRV(y_ref)) != SVt_PVAV) {
        -: 3200:	  croak("cor_test: x and y must be array references");
        -: 3201:	}
        -: 3202:
        -: 3203:	x_av = (AV*)SvRV(x_ref);
        -: 3204:	y_av = (AV*)SvRV(y_ref);
        -: 3205:
        -: 3206:	size_t n_raw = av_len(x_av) + 1;
        -: 3207:	if (n_raw != av_len(y_av) + 1) croak("incompatible dimensions");
        -: 3208:
        -: 3209:	x = safemalloc(n_raw * sizeof(double));
        -: 3210:	y = safemalloc(n_raw * sizeof(double));
        -: 3211:
        -: 3212:	size_t n = 0; /* Final count of pairwise complete observations */
        -: 3213:	for (size_t i = 0; i < n_raw; i++) {
        -: 3214:	  SV **restrict x_val = av_fetch(x_av, i, 0);
        -: 3215:	  SV **restrict y_val = av_fetch(y_av, i, 0);
        -: 3216:
        -: 3217:	  double xv = (x_val && SvOK(*x_val) && looks_like_number(*x_val)) ? SvNV(*x_val) : NAN;
        -: 3218:	  double yv = (y_val && SvOK(*y_val) && looks_like_number(*y_val)) ? SvNV(*y_val) : NAN;
        -: 3219:
        -: 3220:	  /* Pairwise complete observations (skips NAs seamlessly like R) */
        -: 3221:	  if (!isnan(xv) && !isnan(yv)) {
        -: 3222:	      x[n] = xv;
        -: 3223:	      y[n] = yv;
        -: 3224:	      n++;
        -: 3225:	  }
        -: 3226:	}
        -: 3227:
        -: 3228:	if (n < 3) {
        -: 3229:	  Safefree(x); 
        -: 3230:	  Safefree(y);
        -: 3231:	  croak("not enough finite observations");
        -: 3232:	}
        -: 3233:
        -: 3234:	if (is_pearson) {
        -: 3235:	  // Welford's Method for Pearson Correlation
        -: 3236:	  double mean_x = 0.0, mean_y = 0.0, M2_x = 0.0, M2_y = 0.0, cov = 0.0;
        -: 3237:	  for (size_t i = 0; i < n; i++) {
        -: 3238:		   double dx = x[i] - mean_x;
        -: 3239:		   mean_x += dx / (i + 1);
        -: 3240:		   double dy = y[i] - mean_y;
        -: 3241:		   mean_y += dy / (i + 1);
        -: 3242:		   M2_x += dx * (x[i] - mean_x);
        -: 3243:		   M2_y += dy * (y[i] - mean_y);
        -: 3244:		   cov  += dx * (y[i] - mean_y);
        -: 3245:	  }
        -: 3246:	  estimate = (M2_x > 0.0 && M2_y > 0.0) ? cov / sqrt(M2_x * M2_y) : 0.0;
        -: 3247:	  df = n - 2;
        -: 3248:	  statistic = estimate * sqrt(df / (1.0 - estimate * estimate));
        -: 3249:
        -: 3250:	  // Confidence interval using Fisher's Z transform
        -: 3251:	  double z = 0.5 * log((1.0 + estimate) / (1.0 - estimate));
        -: 3252:	  double se = 1.0 / sqrt(n - 3);
        -: 3253:	  double alpha = 1.0 - conf_level;
        -: 3254:	  double q = inverse_normal_cdf(1.0 - alpha/2.0);
        -: 3255:	  ci_lower = tanh(z - q * se);
        -: 3256:	  ci_upper = tanh(z + q * se);
        -: 3257:
        -: 3258:	  // HIGH-PRECISION P-VALUE USING INCOMPLETE BETA
        -: 3259:	  p_value = get_t_pvalue(statistic, df, alternative);
        -: 3260:	} else if (is_kendall) {
        -: 3261:	  int c = 0, d = 0, tie_x = 0, tie_y = 0;
        -: 3262:	  for (size_t i = 0; i < n - 1; i++) {
        -: 3263:		   for (size_t j = i + 1; j < n; j++) {
        -: 3264:		       double sign_x = (x[i] - x[j] > 0) - (x[i] - x[j] < 0);
        -: 3265:		       double sign_y = (y[i] - y[j] > 0) - (y[i] - y[j] < 0);
        -: 3266:
        -: 3267:		       if (sign_x == 0 && sign_y == 0) { /* Joint tie, ignore */ }
        -: 3268:		       else if (sign_x == 0) tie_x++;
        -: 3269:		       else if (sign_y == 0) tie_y++;
        -: 3270:		       else if (sign_x * sign_y > 0) c++;
        -: 3271:		       else d++;
        -: 3272:		   }
        -: 3273:	  }
        -: 3274:	  double denom = sqrt((double)(c + d + tie_x) * (double)(c + d + tie_y));
        -: 3275:	  estimate = (denom == 0.0) ? (0.0/0.0) : (double)(c - d) / denom;
        -: 3276:
        -: 3277:	  bool has_ties = (tie_x > 0 || tie_y > 0);
        -: 3278:	  bool do_exact;
        -: 3279:
        -: 3280:	  /* Mirror R: exact defaults to TRUE if N < 50 and NO ties */
        -: 3281:	  if (!exact_sv || !SvOK(exact_sv)) {
        -: 3282:		   do_exact = (n < 50) && !has_ties;
        -: 3283:	  } else {
        -: 3284:		   do_exact = SvTRUE(exact_sv) ? 1 : 0;
        -: 3285:	  }
        -: 3286:	  // If forced exact but ties exist, R overrides and falls back to approximation anyway
        -: 3287:	  if (do_exact && has_ties) do_exact = 0;
        -: 3288:
        -: 3289:	  if (do_exact) {
        -: 3290:		   double S_stat = c - d;
        -: 3291:		   statistic = c;
        -: 3292:		   p_value = kendall_exact_pvalue(n, S_stat, alternative);
        -: 3293:	  } else {
        -: 3294:		   // Normal approximation for large N or ties
        -: 3295:		   double var_S = n * (n - 1) * (2.0 * n + 5.0) / 18.0;
        -: 3296:		   double S = c - d;
        -: 3297:		   if (continuity) S -= (S > 0 ? 1 : -1);
        -: 3298:		   statistic = S / sqrt(var_S);
        -: 3299:
        -: 3300:		   if (strcmp(alternative, "two.sided") == 0) {
        -: 3301:		       p_value = 2.0 * (1.0 - approx_pnorm(fabs(statistic)));
        -: 3302:		   } else if (strcmp(alternative, "less") == 0) {
        -: 3303:		       p_value = approx_pnorm(statistic);
        -: 3304:		   } else {
        -: 3305:		       p_value = 1.0 - approx_pnorm(statistic);
        -: 3306:		   }
        -: 3307:	  }
        -: 3308:	} else if (is_spearman) {
        -: 3309:	  double *restrict rank_x = safemalloc(n * sizeof(double));
        -: 3310:	  double *restrict rank_y = safemalloc(n * sizeof(double));
        -: 3311:	  compute_ranks(x, rank_x, n);
        -: 3312:	  compute_ranks(y, rank_y, n);
        -: 3313:
        -: 3314:	  // Spearman rho = Pearson r of the ranks (Welford's Method)
        -: 3315:	  double mean_x = 0.0, mean_y = 0.0, M2_x = 0.0, M2_y = 0.0, cov = 0.0;
        -: 3316:	  for (size_t i = 0; i < n; i++) {
        -: 3317:		   double dx = rank_x[i] - mean_x;
        -: 3318:		   mean_x += dx / (i + 1);
        -: 3319:		   double dy = rank_y[i] - mean_y;
        -: 3320:		   mean_y += dy / (i + 1);
        -: 3321:		   M2_x += dx * (rank_x[i] - mean_x);
        -: 3322:		   M2_y += dy * (rank_y[i] - mean_y);
        -: 3323:		   cov  += dx * (rank_y[i] - mean_y);
        -: 3324:	  }
        -: 3325:	  estimate = (M2_x > 0.0 && M2_y > 0.0) ? cov / sqrt(M2_x * M2_y) : 0.0;
        -: 3326:
        -: 3327:	  // S = sum of squared rank differences (R's reported statistic)
        -: 3328:	  double S_stat = 0.0;
        -: 3329:	  for (size_t i = 0; i < n; i++) {
        -: 3330:		   double diff = rank_x[i] - rank_y[i];
        -: 3331:		   S_stat += diff * diff;
        -: 3332:	  }
        -: 3333:
        -: 3334:	  // Ties produce fractional (averaged) ranks — detect them
        -: 3335:	  bool has_ties = 0, do_exact;
        -: 3336:	  for (size_t i = 0; i < n; i++) {
        -: 3337:		   if (rank_x[i] != floor(rank_x[i]) || rank_y[i] != floor(rank_y[i])) {
        -: 3338:		       has_ties = 1;
        -: 3339:		       break;
        -: 3340:		   }
        -: 3341:	  }
        -: 3342:	  if (!exact_sv || !SvOK(exact_sv)) {
        -: 3343:		   do_exact = (n < 10) && !has_ties;
        -: 3344:	  } else {
        -: 3345:		   do_exact = SvTRUE(exact_sv) ? 1 : 0;
        -: 3346:	  }
        -: 3347:
        -: 3348:	  if (do_exact) {
        -: 3349:		   statistic = S_stat;
        -: 3350:		   p_value   = spearman_exact_pvalue(S_stat, n, alternative);
        -: 3351:	  } else {
        -: 3352:		   double r = estimate;
        -: 3353:		   if (continuity)
        -: 3354:		       r *= (1.0 - 1.0 / (2.0 * (n - 1)));
        -: 3355:		   statistic = r * sqrt((n - 2.0) / (1.0 - r * r));
        -: 3356:		   p_value = get_t_pvalue(statistic, (double)(n - 2), alternative);
        -: 3357:	  }
        -: 3358:	  Safefree(rank_x); Safefree(rank_y);
        -: 3359:	} else {
        -: 3360:	  Safefree(x); Safefree(y);
        -: 3361:	  croak("Unknown method");
        -: 3362:	}
        -: 3363:	Safefree(x); Safefree(y);
        -: 3364:	rhv = newHV();
        -: 3365:	hv_stores(rhv, "estimate", newSVnv(estimate));
        -: 3366:	hv_stores(rhv, "p.value", newSVnv(p_value));
        -: 3367:	hv_stores(rhv, "statistic", newSVnv(statistic));
        -: 3368:	hv_stores(rhv, "method", newSVpv(method, 0));
        -: 3369:	hv_stores(rhv, "alternative", newSVpv(alternative, 0));
        -: 3370:	if (is_pearson) {
        -: 3371:	  hv_stores(rhv, "parameter", newSVnv(df));
        -: 3372:	  AV *restrict ci_av = newAV();
        -: 3373:	  av_push(ci_av, newSVnv(ci_lower));
        -: 3374:	  av_push(ci_av, newSVnv(ci_upper));
        -: 3375:	  hv_stores(rhv, "conf.int", newRV_noinc((SV*)ci_av));
        -: 3376:	}
        -: 3377:
        -: 3378:	RETVAL = newRV_noinc((SV*)rhv);
        -: 3379:}
        -: 3380:#line 3381 "LikeR.c"
       21: 3381:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 21
       21: 3382:	ST(0) = RETVAL;
        -: 3383:    }
       21: 3384:    XSRETURN(1);
        -: 3385:}
        -: 3386:
        -: 3387:
        -: 3388:XS_EUPXS(XS_Stats__LikeR_shapiro_test); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_shapiro_test called 6 returned 100% blocks executed 81%
        6: 3389:XS_EUPXS(XS_Stats__LikeR_shapiro_test)
        -: 3390:{
        6: 3391:    dVAR; dXSARGS;
        6: 3391-block  0
call    0 returned 6
        6: 3392:    if (items != 1)
branch  0 taken 0 (fallthrough)
branch  1 taken 6
    #####: 3393:       croak_xs_usage(cv,  "data");
    %%%%%: 3393-block  0
call    0 never executed
        -: 3394:    PERL_UNUSED_VAR(ax); /* -Wall */
        6: 3395:    SP -= items;
        -: 3396:    {
        6: 3397:	SV *	data = ST(0)
        6: 3397-block  0
branch  0 taken 6 (fallthrough)
branch  1 taken 0
        -: 3398:;
        -: 3399:#line 3109 "LikeR.xs"
        -: 3400:	AV *restrict av;
        -: 3401:	HV *restrict ret_hash;
        -: 3402:	size_t n_raw, n = 0;
        -: 3403:	double *restrict x, w = 0.0, p_val = 0.0, mean = 0.0, ssq = 0.0;
        -: 3404:#line 3405 "LikeR.c"
        -: 3405:#line 3114 "LikeR.xs"
        -: 3406:	if (!SvROK(data) || SvTYPE(SvRV(data)) != SVt_PVAV) {
        -: 3407:	  croak("Expected an array reference");
        -: 3408:	}
        -: 3409:
        -: 3410:	av = (AV *)SvRV(data);
        -: 3411:	n_raw = av_len(av) + 1;
        -: 3412:
        -: 3413:	Newx(x, n_raw, double);
        -: 3414:
        -: 3415:	// Extract variables and calculate mean (skipping undefined/NaN values)
        -: 3416:	for (size_t i = 0; i < n_raw; i++) {
        -: 3417:	  SV **restrict elem = av_fetch(av, i, 0);
        -: 3418:	  if (elem && SvOK(*elem)) {
        -: 3419:		   double val = SvNV(*elem);
        -: 3420:		   if (!isnan(val)) {
        -: 3421:		       x[n] = val;
        -: 3422:		       mean += val;
        -: 3423:		       n++;
        -: 3424:		   }
        -: 3425:	  }
        -: 3426:	}
        -: 3427:
        -: 3428:	if (n < 3 || n > 5000) {
        -: 3429:	  Safefree(x);
        -: 3430:	  croak("Sample size must be between 3 and 5000 (R's limit)");
        -: 3431:	}
        -: 3432:
        -: 3433:	mean /= n;
        -: 3434:	// Calculate Sum of Squares */
        -: 3435:	for (size_t i = 0; i < n; i++) {
        -: 3436:	  ssq += (x[i] - mean) * (x[i] - mean);
        -: 3437:	}
        -: 3438:	if (ssq == 0.0) {
        -: 3439:	  Safefree(x);
        -: 3440:	  croak("Data is perfectly constant; cannot compute Shapiro-Wilk test");
        -: 3441:	}
        -: 3442:	qsort(x, n, sizeof(double), compare_doubles);
        -: 3443:
        -: 3444:	// --- Core AS R94 Algorithm: Weights and Statistic W ---
        -: 3445:	if (n == 3) {
        -: 3446:	  double a_val = 0.7071067811865475; /* sqrt(1/2) */
        -: 3447:	  double b_val = a_val * (x[2] - x[0]);
        -: 3448:	  w = (b_val * b_val) / ssq;
        -: 3449:	  if (w < 0.75) w = 0.75; 
        -: 3450:	  // Exact P-value for n=3
        -: 3451:	  p_val = 1.90985931710274 * (asin(sqrt(w)) - 1.04719755119660);
        -: 3452:	} else {
        -: 3453:	  double *restrict m, *restrict a;
        -: 3454:	  double sum_m2 = 0.0, b_val = 0.0;
        -: 3455:	  Newx(m, n, double);
        -: 3456:	  Newx(a, n, double);
        -: 3457:	  for (size_t i = 0; i < n; i++) {
        -: 3458:		   m[i] = inverse_normal_cdf((i + 1.0 - 0.375) / (n + 0.25));
        -: 3459:		   sum_m2 += m[i] * m[i];
        -: 3460:	  }
        -: 3461:	  double u = 1.0 / sqrt((double)n);
        -: 3462:	  double a_n = -2.706056*pow(u,5) + 4.434685*pow(u,4) - 2.071190*pow(u,3) - 0.147981*pow(u,2) + 0.221157*u + m[n-1]/sqrt(sum_m2);
        -: 3463:	  a[n-1] = a_n;
        -: 3464:	  a[0]   = -a_n;
        -: 3465:	  if (n == 4 || n == 5) {
        -: 3466:		   double eps = (sum_m2 - 2.0 * m[n-1]*m[n-1]) / (1.0 - 2.0 * a_n*a_n);
        -: 3467:		   for (unsigned int i = 1; i < n-1; i++) {
        -: 3468:		       a[i] = m[i] / sqrt(eps);
        -: 3469:		   }
        -: 3470:	  } else {
        -: 3471:		   double a_n1 = -3.582633*pow(u,5) + 5.682633*pow(u,4) - 1.752461*pow(u,3) - 0.293762*pow(u,2) + 0.042981*u + m[n-2]/sqrt(sum_m2);
        -: 3472:		   a[n-2] = a_n1;
        -: 3473:		   a[1]   = -a_n1;
        -: 3474:		   double eps = (sum_m2 - 2.0 * m[n-1]*m[n-1] - 2.0 * m[n-2]*m[n-2]) / (1.0 - 2.0 * a_n*a_n - 2.0 * a_n1*a_n1);
        -: 3475:		   for (unsigned int i = 2; i < n-2; i++) {
        -: 3476:		       a[i] = m[i] / sqrt(eps);
        -: 3477:		   }
        -: 3478:	  }
        -: 3479:	  for (size_t i = 0; i < n; i++) {
        -: 3480:		   b_val += a[i] * x[i];
        -: 3481:	  }
        -: 3482:	  w = (b_val * b_val) / ssq;
        -: 3483:	// --- AS R94 P-Value Calculation: High Precision Refinement ---
        -: 3484:	  /* NOTE: p_val is declared in PREINIT above;
        -: 3485:		* do NOT shadow it with a local 'double p_val' here or the result will never reach the caller.
        -: 3486:		*/
        -: 3487:	  double y = log(1.0 - w);
        -: 3488:	  double z;
        -: 3489:	  if (n <= 11) {
        -: 3490:		   // Royston's branch for 4 <= n <= 11 (AS R94, small-sample path).
        -: 3491:		   // gamma is the upper bound on y = log(1-W);
        -: 3492:		   // if y reaches gamma the p-value is essentially zero
        -: 3493:		   double nn = (double)n;
        -: 3494:		   double gamma = 0.459 * nn - 2.273;
        -: 3495:		   if (y >= gamma) {
        -: 3496:		       p_val = 1e-19;
        -: 3497:		   } else {
        -: 3498:		       // Horner-form polynomials in n for mu and log(sigma)
        -: 3499:		       double mu     = 0.544  + nn * (-0.39978  + nn * ( 0.025054  - nn * 0.0006714));
        -: 3500:		       double sig_val= 1.3822 + nn * (-0.77857  + nn * ( 0.062767  - nn * 0.0020322));
        -: 3501:		       double sigma  = exp(sig_val);
        -: 3502:		       z = (-log(gamma - y) - mu) / sigma;
        -: 3503:		       /* Upper-tail probability P(Z > z): small W → large z → small p-value.
        -: 3504:		       */
        -: 3505:		       p_val = 0.5 * erfc(z * M_SQRT1_2);
        -: 3506:		   }
        -: 3507:	  } else {
        -: 3508:		   // Royston's branch for n >= 12 (AS R94, large-sample path)
        -: 3509:		   double ln_n   = log((double)n);
        -: 3510:		   // Horner-form polynomials in log(n) for mu and log(sigma). */
        -: 3511:		   double mu     = -1.5861 + ln_n * (-0.31082 + ln_n * (-0.083751 + ln_n * 0.0038915));
        -: 3512:		   double sig_val= -0.4803 + ln_n * (-0.082676 + ln_n * 0.0030302);
        -: 3513:		   double sigma  = exp(sig_val);
        -: 3514:		   z = (y - mu) / sigma;
        -: 3515:		   p_val = 0.5 * erfc(z * M_SQRT1_2);
        -: 3516:	  }
        -: 3517:	  // Clamp the p-value
        -: 3518:	  if (p_val > 1.0) p_val = 1.0;
        -: 3519:	  if (p_val < 0.0) p_val = 0.0;
        -: 3520:
        -: 3521:	  Safefree(m); m = NULL;  Safefree(a); a = NULL;
        -: 3522:	}
        -: 3523:	Safefree(x); x = NULL;
        -: 3524:	ret_hash = newHV();
        -: 3525:	hv_stores(ret_hash, "statistic", newSVnv(w));
        -: 3526:	hv_stores(ret_hash, "W",         newSVnv(w));
        -: 3527:	hv_stores(ret_hash, "p_value",   newSVnv(p_val));
        -: 3528:	hv_stores(ret_hash, "p.value",   newSVnv(p_val));
        -: 3529:	EXTEND(SP, 1);
        -: 3530:	PUSHs(sv_2mortal(newRV_noinc((SV *)ret_hash)));
        -: 3531:#line 3532 "LikeR.c"
        6: 3532:	PUTBACK;
        6: 3533:	return;
        -: 3534:    }
        -: 3535:}
        -: 3536:
        -: 3537:
        -: 3538:XS_EUPXS(XS_Stats__LikeR_min); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_min called 30 returned 90% blocks executed 94%
       30: 3539:XS_EUPXS(XS_Stats__LikeR_min)
        -: 3540:{
       30: 3541:    dVAR; dXSARGS;
       30: 3541-block  0
call    0 returned 30
        -: 3542:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3543:    PERL_UNUSED_VAR(items); /* -W */
        -: 3544:    {
        -: 3545:	double	RETVAL;
      30*: 3546:	dXSTARG;
branch  0 taken 30 (fallthrough)
branch  1 taken 0
       30: 3546-block  0
    %%%%%: 3546-block  1
call    2 never executed
       30: 3546-block  2
        -: 3547:#line 3243 "LikeR.xs"
        -: 3548:		double min_val = 0.0;
        -: 3549:		size_t count = 0;
        -: 3550:		bool first = TRUE;
        -: 3551:#line 3552 "LikeR.c"
        -: 3552:#line 3247 "LikeR.xs"
        -: 3553:		for (unsigned short int i = 0; i < items; i++) {
        -: 3554:			SV* restrict arg = ST(i);
        -: 3555:			if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 3556:				AV* restrict av = (AV*)SvRV(arg);
        -: 3557:				size_t len = av_len(av) + 1;
        -: 3558:				for (size_t j = 0; j < len; j++) {
        -: 3559:				  SV** restrict tv = av_fetch(av, j, 0);
        -: 3560:				  if (tv && SvOK(*tv)) {
        -: 3561:						double val = SvNV(*tv);
        -: 3562:						if (first || val < min_val) {
        -: 3563:							 min_val = val;
        -: 3564:							 first = FALSE;
        -: 3565:						}
        -: 3566:						count++;
        -: 3567:				  }
        -: 3568:				}
        -: 3569:			} else if (SvOK(arg)) {
        -: 3570:				double val = SvNV(arg);
        -: 3571:				if (first || val < min_val) {
        -: 3572:				  min_val = val;
        -: 3573:				  first = FALSE;
        -: 3574:				}
        -: 3575:				count++;
        -: 3576:			}
        -: 3577:		}
        -: 3578:		if (count == 0) croak("min needs >= 1 numeric element");
        -: 3579:		RETVAL = min_val;
        -: 3580:#line 3581 "LikeR.c"
      27*: 3581:	TARGn((double)RETVAL, 1);
       27: 3581-block  0
branch  0 taken 0 (fallthrough)
branch  1 taken 27
    %%%%%: 3581-block  1
       27: 3581-block  2
call    2 returned 27
       27: 3582:	ST(0) = TARG;
        -: 3583:    }
       27: 3584:    XSRETURN(1);
       27: 3584-block  0
        -: 3585:}
        -: 3586:
        -: 3587:
        -: 3588:XS_EUPXS(XS_Stats__LikeR_max); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_max called 33 returned 91% blocks executed 94%
       33: 3589:XS_EUPXS(XS_Stats__LikeR_max)
        -: 3590:{
       33: 3591:    dVAR; dXSARGS;
       33: 3591-block  0
call    0 returned 33
        -: 3592:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3593:    PERL_UNUSED_VAR(items); /* -W */
        -: 3594:    {
        -: 3595:	double	RETVAL;
      33*: 3596:	dXSTARG;
branch  0 taken 33 (fallthrough)
branch  1 taken 0
       33: 3596-block  0
    %%%%%: 3596-block  1
call    2 never executed
       33: 3596-block  2
        -: 3597:#line 3280 "LikeR.xs"
        -: 3598:	  double max_val = 0.0;
        -: 3599:	  size_t count = 0;
        -: 3600:	  bool first = TRUE;
        -: 3601:#line 3602 "LikeR.c"
        -: 3602:#line 3284 "LikeR.xs"
        -: 3603:	  for (size_t i = 0; i < items; i++) {
        -: 3604:		   SV* restrict arg = ST(i);
        -: 3605:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 3606:		       AV* restrict av = (AV*)SvRV(arg);
        -: 3607:		       size_t len = av_len(av) + 1;
        -: 3608:		       for (size_t j = 0; j < len; j++) {
        -: 3609:		           SV** restrict tv = av_fetch(av, j, 0);
        -: 3610:		           if (tv && SvOK(*tv)) {
        -: 3611:		               double val = SvNV(*tv);
        -: 3612:		               if (first || val > max_val) {
        -: 3613:		                   max_val = val;
        -: 3614:		                   first = FALSE;
        -: 3615:		               }
        -: 3616:		               count++;
        -: 3617:		           }
        -: 3618:		       }
        -: 3619:		   } else if (SvOK(arg)) {
        -: 3620:		       double val = SvNV(arg);
        -: 3621:		       if (first || val > max_val) {
        -: 3622:		           max_val = val;
        -: 3623:		           first = FALSE;
        -: 3624:		       }
        -: 3625:		       count++;
        -: 3626:		   }
        -: 3627:	  }
        -: 3628:	  if (count == 0) croak("max needs >= 1 numeric element");
        -: 3629:	  RETVAL = max_val;
        -: 3630:#line 3631 "LikeR.c"
      30*: 3631:	TARGn((double)RETVAL, 1);
       30: 3631-block  0
branch  0 taken 0 (fallthrough)
branch  1 taken 30
    %%%%%: 3631-block  1
       30: 3631-block  2
call    2 returned 30
       30: 3632:	ST(0) = TARG;
        -: 3633:    }
       30: 3634:    XSRETURN(1);
       30: 3634-block  0
        -: 3635:}
        -: 3636:
        -: 3637:
        -: 3638:XS_EUPXS(XS_Stats__LikeR_runif); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_runif called 12 returned 100% blocks executed 86%
       12: 3639:XS_EUPXS(XS_Stats__LikeR_runif)
        -: 3640:{
       12: 3641:    dVAR; dXSARGS;
       12: 3641-block  0
call    0 returned 12
branch  1 taken 0 (fallthrough)
branch  2 taken 12
        -: 3642:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3643:    PERL_UNUSED_VAR(items); /* -W */
        -: 3644:    {
        -: 3645:	SV *	RETVAL;
        -: 3646:#line 3316 "LikeR.xs"
        -: 3647:{
        -: 3648:	size_t n = 0;
        -: 3649:	double min = 0.0, max = 1.0;
        -: 3650:
        -: 3651:	// Flags to track what has been assigned
        -: 3652:	bool n_set = 0, min_set = 0, max_set = 0;
        -: 3653:
        -: 3654:	unsigned int i = 0;
        -: 3655:
        -: 3656:	if (items == 0) {
        -: 3657:	  croak("Usage: runif(n, [min=0], [max=1]) or runif(n => $n, ...)");
        -: 3658:	}
        -: 3659:
        -: 3660:	while (i < items) {
        -: 3661:		// 1. Check if the current argument is a string key for a named parameter
        -: 3662:		if (i + 1 < items && SvPOK(ST(i))) {
        -: 3663:			char *restrict key = SvPV_nolen(ST(i));
        -: 3664:			if (strEQ(key, "n")) {
        -: 3665:				n = (size_t)SvUV(ST(i+1));
        -: 3666:				n_set = 1;
        -: 3667:				i += 2;
        -: 3668:				continue;
        -: 3669:			} else if (strEQ(key, "min")) {
        -: 3670:				min = SvNV(ST(i+1));
        -: 3671:				min_set = 1;
        -: 3672:				i += 2;
        -: 3673:				continue;
        -: 3674:			} else if (strEQ(key, "max")) {
        -: 3675:				max = SvNV(ST(i+1));
        -: 3676:				max_set = 1;
        -: 3677:				i += 2;
        -: 3678:				continue;
        -: 3679:			}
        -: 3680:		}
        -: 3681:
        -: 3682:		// 2. Fallback to positional parsing if it's not a recognized key
        -: 3683:		if (!n_set) {
        -: 3684:			n = (size_t)SvUV(ST(i));
        -: 3685:			n_set = 1;
        -: 3686:		} else if (!min_set) {
        -: 3687:			min = SvNV(ST(i));
        -: 3688:			min_set = 1;
        -: 3689:		} else if (!max_set) {
        -: 3690:			max = SvNV(ST(i));
        -: 3691:			max_set = 1;
        -: 3692:		} else {
        -: 3693:			croak("Too many arguments or unrecognized parameter passed to runif()");
        -: 3694:		}
        -: 3695:		i++;
        -: 3696:	}
        -: 3697:	if (!n_set) {
        -: 3698:		croak("runif() requires at least the 'n' parameter");
        -: 3699:	}
        -: 3700:	// Ensure PRNG is seeded
        -: 3701:	AUTO_SEED_PRNG();
        -: 3702:	AV *restrict results = newAV();
        -: 3703:	if (n > 0) {
        -: 3704:		av_extend(results, n - 1);
        -: 3705:	}
        -: 3706:	const double range = max - min;
        -: 3707:	for (size_t j = 0; j < n; j++) {
        -: 3708:		double r;
        -: 3709:		if (max < min) {
        -: 3710:			r = NAN; // R behavior for inverted ranges
        -: 3711:		} else {
        -: 3712:			r = min + range * Drand01();
        -: 3713:		}
        -: 3714:		av_push(results, newSVnv(r));
        -: 3715:	}
        -: 3716:	RETVAL = newRV_noinc((SV*)results);
        -: 3717:}
        -: 3718:#line 3719 "LikeR.c"
       12: 3719:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 12
       12: 3720:	ST(0) = RETVAL;
        -: 3721:    }
       12: 3722:    XSRETURN(1);
        -: 3723:}
        -: 3724:
        -: 3725:
        -: 3726:XS_EUPXS(XS_Stats__LikeR_rbinom); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_rbinom called 36 returned 58% blocks executed 90%
       36: 3727:XS_EUPXS(XS_Stats__LikeR_rbinom)
        -: 3728:{
       36: 3729:    dVAR; dXSARGS;
       36: 3729-block  0
call    0 returned 36
branch  1 taken 0 (fallthrough)
branch  2 taken 36
        -: 3730:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3731:    PERL_UNUSED_VAR(items); /* -W */
        -: 3732:    {
        -: 3733:	SV *	RETVAL;
        -: 3734:#line 3392 "LikeR.xs"
        -: 3735:	{
        -: 3736:	  // Auto-seed the PRNG if the Perl script hasn't done so yet
        -: 3737:	  AUTO_SEED_PRNG();
        -: 3738:	  if (items % 2 != 0)
        -: 3739:		   croak("Usage: rbinom(n => 10, size => 100, prob => 0.5)");
        -: 3740:	  //Parse named arguments
        -: 3741:	  size_t n = 0, size = 0;
        -: 3742:	  double prob = 0.5;
        -: 3743:
        -: 3744:	  bool size_set = false, prob_set = false;
        -: 3745:
        -: 3746:	  for (unsigned short i = 0; i < items; i += 2) {
        -: 3747:		   const char* restrict key = SvPV_nolen(ST(i));
        -: 3748:		   SV* restrict val = ST(i + 1);
        -: 3749:
        -: 3750:		   if      (strEQ(key, "n"))      n    = (unsigned int)SvUV(val);
        -: 3751:		   else if (strEQ(key, "size")) { size = (unsigned int)SvUV(val); size_set = true; }
        -: 3752:		   else if (strEQ(key, "prob")) { prob = SvNV(val); prob_set = true; }
        -: 3753:		   else croak("rbinom: unknown argument '%s'", key);
        -: 3754:	  }
        -: 3755:
        -: 3756:	  // R requires size and prob to be explicitly passed in rbinom
        -: 3757:	  if (!size_set || !prob_set) croak("rbinom: 'size' and 'prob' are required arguments");
        -: 3758:	  if (prob < 0.0 || prob > 1.0) croak("rbinom: prob must be between 0 and 1");
        -: 3759:
        -: 3760:	  AV *restrict result_av = newAV();
        -: 3761:	  if (n > 0) {
        -: 3762:		   av_extend(result_av, n - 1);
        -: 3763:		   for (unsigned int i = 0; i < n; i++) {
        -: 3764:		       av_store(result_av, i, newSVuv(generate_binomial(size, prob)));
        -: 3765:		   }
        -: 3766:	  }
        -: 3767:
        -: 3768:	  RETVAL = newRV_noinc((SV*)result_av);
        -: 3769:	}
        -: 3770:#line 3771 "LikeR.c"
       21: 3771:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 21
       21: 3772:	ST(0) = RETVAL;
        -: 3773:    }
       21: 3774:    XSRETURN(1);
        -: 3775:}
        -: 3776:
        -: 3777:
        -: 3778:XS_EUPXS(XS_Stats__LikeR_hist); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_hist called 27 returned 56% blocks executed 83%
       27: 3779:XS_EUPXS(XS_Stats__LikeR_hist)
        -: 3780:{
       27: 3781:    dVAR; dXSARGS;
       27: 3781-block  0
call    0 returned 27
       27: 3782:    if (items < 1)
branch  0 taken 0 (fallthrough)
branch  1 taken 27
    #####: 3783:       croak_xs_usage(cv,  "x_sv, ...");
    %%%%%: 3783-block  0
call    0 never executed
        -: 3784:    {
        -: 3785:	SV *	RETVAL;
       27: 3786:	SV*	x_sv = ST(0)
       27: 3786-block  0
branch  0 taken 24 (fallthrough)
branch  1 taken 3
        -: 3787:;
        -: 3788:#line 3433 "LikeR.xs"
        -: 3789:	{
        -: 3790:		// 1. Validate Input
        -: 3791:		if (!SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 3792:			croak("hist: first argument must be an array reference");
        -: 3793:
        -: 3794:		AV*restrict x_av = (AV*)SvRV(x_sv);
        -: 3795:		size_t n_raw = av_len(x_av) + 1;
        -: 3796:		if (n_raw == 0) croak("hist: input array is empty");
        -: 3797:
        -: 3798:		// 2. Extract Data & Find Range
        -: 3799:		double *restrict x;
        -: 3800:		Newx(x, n_raw, double);
        -: 3801:		size_t n = 0;
        -: 3802:		double min_val = DBL_MAX, max_val = -DBL_MAX;
        -: 3803:
        -: 3804:		for (size_t i = 0; i < n_raw; i++) {
        -: 3805:			SV**restrict tv = av_fetch(x_av, i, 0);
        -: 3806:			if (tv && SvOK(*tv)) {
        -: 3807:				 double val = SvNV(*tv);
        -: 3808:				 x[n++] = val;
        -: 3809:				 if (val < min_val) min_val = val;
        -: 3810:				 if (val > max_val) max_val = val;
        -: 3811:			}
        -: 3812:		}
        -: 3813:		if (n == 0) {
        -: 3814:			Safefree(x);
        -: 3815:			croak("hist: input contains no valid numeric data");
        -: 3816:		}
        -: 3817:		// 3. Determine Bin Count (Sturges default or user-provided)
        -: 3818:		size_t n_bins = 0;
        -: 3819:
        -: 3820:		if (items == 2) {
        -: 3821:			// Support pure positional argument: hist($data, 22)
        -: 3822:			n_bins = (size_t)SvIV(ST(1));
        -: 3823:		} else if (items > 2) {
        -: 3824:			/* Support named parameters even if mixed with positional arguments */
        -: 3825:			for (unsigned short i = 1; i < items - 1; i++) {
        -: 3826:				 /* Make sure the SV holds a string before doing string comparison */
        -: 3827:				 if (SvPOK(ST(i)) && strEQ(SvPV_nolen(ST(i)), "breaks")) {
        -: 3828:				     n_bins = (size_t)SvIV(ST(i+1));
        -: 3829:				     break;
        -: 3830:				 }
        -: 3831:			}
        -: 3832:			/* Fallback: if 'breaks' wasn't found but a positional number was given first */
        -: 3833:			if (n_bins == 0 && looks_like_number(ST(1))) {
        -: 3834:				 n_bins = (size_t)SvIV(ST(1));
        -: 3835:			}
        -: 3836:		}
        -: 3837:		if (n_bins == 0) n_bins = calculate_sturges_bins(n);
        -: 3838:		// 4. Allocate Result Arrays
        -: 3839:		double *restrict breaks, *restrict mids, *restrict density;
        -: 3840:		size_t *restrict counts;
        -: 3841:		Newx(breaks,  n_bins + 1, double);
        -: 3842:		Newx(mids,    n_bins,     double);
        -: 3843:		Newx(density, n_bins,     double);
        -: 3844:		Newx(counts,  n_bins,     size_t);
        -: 3845:
        -: 3846:		// Generate simple linear breaks
        -: 3847:		double step = (max_val - min_val) / (double)n_bins;
        -: 3848:		for (size_t i = 0; i <= n_bins; i++) {
        -: 3849:			breaks[i] = min_val + (double)i * step;
        -: 3850:		}
        -: 3851:
        -: 3852:		// 5. Compute Statistics
        -: 3853:		compute_hist_logic(x, n, breaks, n_bins, counts, mids, density);
        -: 3854:
        -: 3855:		// 6. Build Return HashRef
        -: 3856:		HV*restrict res_hv = newHV();
        -: 3857:		AV*restrict av_breaks  = newAV();
        -: 3858:		AV*restrict av_counts  = newAV();
        -: 3859:		AV*restrict av_mids    = newAV();
        -: 3860:		AV*restrict av_density = newAV();
        -: 3861:		for (size_t i = 0; i <= n_bins; i++) {
        -: 3862:			av_push(av_breaks, newSVnv(breaks[i]));
        -: 3863:			if (i < n_bins) {
        -: 3864:				 av_push(av_counts,  newSViv(counts[i]));
        -: 3865:				 av_push(av_mids,    newSVnv(mids[i]));
        -: 3866:				 av_push(av_density, newSVnv(density[i]));
        -: 3867:			}
        -: 3868:		}
        -: 3869:		hv_stores(res_hv, "breaks",  newRV_noinc((SV*)av_breaks));
        -: 3870:		hv_stores(res_hv, "counts",  newRV_noinc((SV*)av_counts));
        -: 3871:		hv_stores(res_hv, "mids",    newRV_noinc((SV*)av_mids));
        -: 3872:		hv_stores(res_hv, "density", newRV_noinc((SV*)av_density));
        -: 3873:
        -: 3874:		// Clean
        -: 3875:		Safefree(x); Safefree(breaks); Safefree(mids);
        -: 3876:		Safefree(density); Safefree(counts);
        -: 3877:
        -: 3878:		RETVAL = newRV_noinc((SV*)res_hv);
        -: 3879:	}
        -: 3880:#line 3881 "LikeR.c"
       15: 3881:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 15
       15: 3882:	ST(0) = RETVAL;
        -: 3883:    }
       15: 3884:    XSRETURN(1);
        -: 3885:}
        -: 3886:
        -: 3887:
        -: 3888:XS_EUPXS(XS_Stats__LikeR_quantile); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_quantile called 12 returned 100% blocks executed 81%
       12: 3889:XS_EUPXS(XS_Stats__LikeR_quantile)
        -: 3890:{
       12: 3891:    dVAR; dXSARGS;
       12: 3891-block  0
call    0 returned 12
branch  1 taken 12 (fallthrough)
branch  2 taken 0
        -: 3892:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3893:    PERL_UNUSED_VAR(items); /* -W */
        -: 3894:    {
        -: 3895:	SV *	RETVAL;
        -: 3896:#line 3529 "LikeR.xs"
        -: 3897:	{
        -: 3898:		SV *restrict x_sv = NULL;
        -: 3899:		SV *restrict probs_sv = NULL;
        -: 3900:		int arg_idx = 0;
        -: 3901:
        -: 3902:		/* --- 1. Consume first positional arg as 'x' if it's an array ref --- */
        -: 3903:		if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 3904:			 x_sv = ST(arg_idx);
        -: 3905:			 arg_idx++;
        -: 3906:		}
        -: 3907:
        -: 3908:		/* --- 2. Remaining args must be key-value pairs --- */
        -: 3909:		if ((items - arg_idx) % 2 != 0)
        -: 3910:			 croak("Usage: quantile(\\@data, probs => \\@probs)  OR  quantile(x => \\@data, probs => \\@probs)");
        -: 3911:
        -: 3912:		for (; arg_idx < items; arg_idx += 2) {
        -: 3913:			 const char *restrict key = SvPV_nolen(ST(arg_idx));
        -: 3914:			 SV *restrict val = ST(arg_idx + 1);
        -: 3915:
        -: 3916:			 if      (strEQ(key, "x"))     x_sv     = val;
        -: 3917:			 else if (strEQ(key, "probs")) probs_sv = val;
        -: 3918:			 else croak("quantile: unknown argument '%s'", key);
        -: 3919:		}
        -: 3920:		if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 3921:			croak("quantile: 'x' must be an array reference");
        -: 3922:		AV *restrict x_av = (AV*)SvRV(x_sv);
        -: 3923:		size_t n_raw = av_len(x_av) + 1;
        -: 3924:		if (n_raw == 0) croak("quantile: 'x' is empty");
        -: 3925:
        -: 3926:		/* --- Extract valid numeric data & drop NAs --- */
        -: 3927:		double *restrict x;
        -: 3928:		Newx(x, n_raw, double);
        -: 3929:		size_t n = 0;
        -: 3930:		for (size_t i = 0; i < n_raw; i++) {
        -: 3931:			SV **restrict tv = av_fetch(x_av, i, 0);
        -: 3932:			if (tv && SvOK(*tv)) {
        -: 3933:				 x[n++] = SvNV(*tv);
        -: 3934:			}
        -: 3935:		}
        -: 3936:		if (n == 0) {
        -: 3937:			Safefree(x);
        -: 3938:			croak("quantile: 'x' contains no valid numbers");
        -: 3939:		}
        -: 3940:		// --- Sort Data for Quantile Math ---
        -: 3941:		qsort(x, n, sizeof(double), compare_doubles);
        -: 3942:		// --- Parse Probabilities (Default matches R's c(0, .25, .5, .75, 1)) ---
        -: 3943:		double default_probs[] = {0.0, 0.25, 0.50, 0.75, 1.0};
        -: 3944:		unsigned int n_probs = 5;
        -: 3945:		double *restrict probs;
        -: 3946:
        -: 3947:		if (probs_sv && SvROK(probs_sv) && SvTYPE(SvRV(probs_sv)) == SVt_PVAV) {
        -: 3948:			AV *restrict p_av = (AV*)SvRV(probs_sv);
        -: 3949:			n_probs = av_len(p_av) + 1;
        -: 3950:			Newx(probs, n_probs, double);
        -: 3951:			for (unsigned int i = 0; i < n_probs; i++) {
        -: 3952:				 SV **tv = av_fetch(p_av, i, 0);
        -: 3953:				 probs[i] = (tv && SvOK(*tv)) ? SvNV(*tv) : 0.0;
        -: 3954:				 if (probs[i] < 0.0 || probs[i] > 1.0) {
        -: 3955:				     Safefree(x); Safefree(probs);
        -: 3956:				     croak("quantile: probabilities must be between 0 and 1");
        -: 3957:				 }
        -: 3958:			}
        -: 3959:		} else {
        -: 3960:			Newx(probs, n_probs, double);
        -: 3961:			for (unsigned int i = 0; i < n_probs; i++) probs[i] = default_probs[i];
        -: 3962:		}
        -: 3963:
        -: 3964:		/* --- Calculate Quantiles (R Type 7 Algorithm) --- */
        -: 3965:		HV *restrict res_hv = newHV();
        -: 3966:
        -: 3967:		for (size_t i = 0; i < n_probs; i++) {
        -: 3968:			double p = probs[i], q = 0.0;
        -: 3969:
        -: 3970:			if (n == 1) {
        -: 3971:				 q = x[0];
        -: 3972:			} else if (p == 1.0) {
        -: 3973:				 q = x[n - 1]; /* Prevent out-of-bounds mapping */
        -: 3974:			} else if (p == 0.0) {
        -: 3975:				 q = x[0];
        -: 3976:			} else {
        -: 3977:				 /* Continuous sample quantile interpolation (Type 7) */
        -: 3978:				 double h = (n - 1) * p;
        -: 3979:				 unsigned int j = (unsigned int)h; /* floor via cast */
        -: 3980:				 double gamma = h - j;
        -: 3981:				 q = (1.0 - gamma) * x[j] + gamma * x[j + 1];
        -: 3982:			}
        -: 3983:
        -: 3984:			/* Format hash key to exactly match R's naming convention ("25%", "33.3%") */
        -: 3985:			char key[32];
        -: 3986:			double pct = p * 100.0;
        -: 3987:
        -: 3988:			if (pct == (unsigned int)pct) {
        -: 3989:				 snprintf(key, sizeof(key), "%.0f%%", pct);
        -: 3990:			} else {
        -: 3991:				 snprintf(key, sizeof(key), "%.1f%%", pct);
        -: 3992:			}
        -: 3993:
        -: 3994:			hv_store(res_hv, key, strlen(key), newSVnv(q), 0);
        -: 3995:		}
        -: 3996:
        -: 3997:		Safefree(x);
        -: 3998:		Safefree(probs);
        -: 3999:
        -: 4000:		RETVAL = newRV_noinc((SV*)res_hv);
        -: 4001:	}
        -: 4002:#line 4003 "LikeR.c"
       12: 4003:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 12
       12: 4004:	ST(0) = RETVAL;
        -: 4005:    }
       12: 4006:    XSRETURN(1);
        -: 4007:}
        -: 4008:
        -: 4009:
        -: 4010:XS_EUPXS(XS_Stats__LikeR_mean); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_mean called 117 returned 97% blocks executed 97%
      117: 4011:XS_EUPXS(XS_Stats__LikeR_mean)
        -: 4012:{
      117: 4013:    dVAR; dXSARGS;
      117: 4013-block  0
call    0 returned 117
        -: 4014:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4015:    PERL_UNUSED_VAR(items); /* -W */
        -: 4016:    {
        -: 4017:	double	RETVAL;
     117*: 4018:	dXSTARG;
branch  0 taken 117 (fallthrough)
branch  1 taken 0
      117: 4018-block  0
    %%%%%: 4018-block  1
call    2 never executed
      117: 4018-block  2
        -: 4019:#line 3641 "LikeR.xs"
        -: 4020:	  double total = 0;
        -: 4021:	  size_t count = 0;
        -: 4022:#line 4023 "LikeR.c"
        -: 4023:#line 3644 "LikeR.xs"
        -: 4024:	  for (size_t i = 0; i < items; i++) {
        -: 4025:		   SV*restrict arg = ST(i);
        -: 4026:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4027:		       AV*restrict av = (AV*)SvRV(arg);
        -: 4028:		       size_t len = av_len(av) + 1;
        -: 4029:		       for (size_t j = 0; j < len; j++) {
        -: 4030:		           SV**restrict tv = av_fetch(av, j, 0);
        -: 4031:		           if (tv && SvOK(*tv)) { total += SvNV(*tv); count++; }
        -: 4032:		       }
        -: 4033:		   } else if (SvOK(arg)) {
        -: 4034:		       total += SvNV(arg); count++;
        -: 4035:		   }
        -: 4036:	  }
        -: 4037:	  if (count == 0) croak("mean needs >= 1 element");
        -: 4038:	  RETVAL = total / count;
        -: 4039:#line 4040 "LikeR.c"
      114: 4040:	TARGn((double)RETVAL, 1);
      114: 4040-block  0
branch  0 taken 90 (fallthrough)
branch  1 taken 24
       90: 4040-block  1
       24: 4040-block  2
call    2 returned 24
      114: 4041:	ST(0) = TARG;
        -: 4042:    }
      114: 4043:    XSRETURN(1);
      114: 4043-block  0
        -: 4044:}
        -: 4045:
        -: 4046:
        -: 4047:XS_EUPXS(XS_Stats__LikeR_sd); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_sd called 15 returned 80% blocks executed 93%
       15: 4048:XS_EUPXS(XS_Stats__LikeR_sd)
        -: 4049:{
       15: 4050:    dVAR; dXSARGS;
       15: 4050-block  0
call    0 returned 15
        -: 4051:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4052:    PERL_UNUSED_VAR(items); /* -W */
        -: 4053:    {
        -: 4054:	double	RETVAL;
      15*: 4055:	dXSTARG;
branch  0 taken 15 (fallthrough)
branch  1 taken 0
       15: 4055-block  0
    %%%%%: 4055-block  1
call    2 never executed
       15: 4055-block  2
        -: 4056:#line 3665 "LikeR.xs"
        -: 4057:	  double mean = 0.0, M2 = 0.0;
        -: 4058:	  size_t count = 0;
        -: 4059:#line 4060 "LikeR.c"
        -: 4060:#line 3668 "LikeR.xs"
        -: 4061:	  // Single Pass Standard Deviation via Welford's Algorithm
        -: 4062:	  for (size_t i = 0; i < items; i++) {
        -: 4063:		   SV* restrict arg = ST(i);
        -: 4064:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4065:		       AV*restrict av = (AV*)SvRV(arg);
        -: 4066:		       size_t len = av_len(av) + 1;
        -: 4067:		       for (size_t j = 0; j < len; j++) {
        -: 4068:		           SV**restrict tv = av_fetch(av, j, 0);
        -: 4069:		           if (tv && SvOK(*tv)) {
        -: 4070:		               count++;
        -: 4071:		               double val = SvNV(*tv);
        -: 4072:		               double delta = val - mean;
        -: 4073:		               mean += delta / count;
        -: 4074:		               M2 += delta * (val - mean);
        -: 4075:		           }
        -: 4076:		       }
        -: 4077:		   } else if (SvOK(arg)) {
        -: 4078:		       count++;
        -: 4079:		       double val = SvNV(arg);
        -: 4080:		       double delta = val - mean;
        -: 4081:		       mean += delta / count;
        -: 4082:		       M2 += delta * (val - mean);
        -: 4083:		   }
        -: 4084:	  }
        -: 4085:	  if (count < 2) croak("stdev needs >= 2 elements");
        -: 4086:	  RETVAL = sqrt(M2 / (count - 1));
        -: 4087:#line 4088 "LikeR.c"
      12*: 4088:	TARGn((double)RETVAL, 1);
       12: 4088-block  0
branch  0 taken 0 (fallthrough)
branch  1 taken 12
    %%%%%: 4088-block  1
       12: 4088-block  2
call    2 returned 12
       12: 4089:	ST(0) = TARG;
        -: 4090:    }
       12: 4091:    XSRETURN(1);
       12: 4091-block  0
        -: 4092:}
        -: 4093:
        -: 4094:
        -: 4095:XS_EUPXS(XS_Stats__LikeR_var); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_var called 18 returned 83% blocks executed 97%
       18: 4096:XS_EUPXS(XS_Stats__LikeR_var)
        -: 4097:{
       18: 4098:    dVAR; dXSARGS;
       18: 4098-block  0
call    0 returned 18
        -: 4099:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4100:    PERL_UNUSED_VAR(items); /* -W */
        -: 4101:    {
        -: 4102:	double	RETVAL;
      18*: 4103:	dXSTARG;
branch  0 taken 18 (fallthrough)
branch  1 taken 0
       18: 4103-block  0
    %%%%%: 4103-block  1
call    2 never executed
       18: 4103-block  2
        -: 4104:#line 3700 "LikeR.xs"
        -: 4105:	  double mean = 0.0, M2 = 0.0;
        -: 4106:	  size_t count = 0;
        -: 4107:#line 4108 "LikeR.c"
        -: 4108:#line 3703 "LikeR.xs"
        -: 4109:	  // Single Pass Variance via Welford's Algorithm
        -: 4110:	  for (size_t i = 0; i < items; i++) {
        -: 4111:		   SV*restrict arg = ST(i);
        -: 4112:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4113:		       AV*restrict av = (AV*)SvRV(arg);
        -: 4114:		       size_t len = av_len(av) + 1;
        -: 4115:		       for (size_t j = 0; j < len; j++) {
        -: 4116:		           SV**restrict tv = av_fetch(av, j, 0);
        -: 4117:		           if (tv && SvOK(*tv)) { 
        -: 4118:		               count++;
        -: 4119:		               double val = SvNV(*tv);
        -: 4120:		               double delta = val - mean;
        -: 4121:		               mean += delta / count;
        -: 4122:		               M2 += delta * (val - mean);
        -: 4123:		           }
        -: 4124:		       }
        -: 4125:		   } else if (SvOK(arg)) {
        -: 4126:		       count++;
        -: 4127:		       double val = SvNV(arg);
        -: 4128:		       double delta = val - mean;
        -: 4129:		       mean += delta / count;
        -: 4130:		       M2 += delta * (val - mean);
        -: 4131:		   }
        -: 4132:	  }
        -: 4133:	  if (count < 2) croak("var needs >= 2 elements");
        -: 4134:	  RETVAL = M2 / (count - 1);
        -: 4135:#line 4136 "LikeR.c"
       15: 4136:	TARGn((double)RETVAL, 1);
       15: 4136-block  0
branch  0 taken 3 (fallthrough)
branch  1 taken 12
        3: 4136-block  1
       12: 4136-block  2
call    2 returned 12
       15: 4137:	ST(0) = TARG;
        -: 4138:    }
       15: 4139:    XSRETURN(1);
       15: 4139-block  0
        -: 4140:}
        -: 4141:
        -: 4142:
        -: 4143:XS_EUPXS(XS_Stats__LikeR_t_test); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_t_test called 141 returned 89% blocks executed 91%
      141: 4144:XS_EUPXS(XS_Stats__LikeR_t_test)
        -: 4145:{
      141: 4146:    dVAR; dXSARGS;
      141: 4146-block  0
call    0 returned 141
branch  1 taken 141 (fallthrough)
branch  2 taken 0
        -: 4147:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4148:    PERL_UNUSED_VAR(items); /* -W */
        -: 4149:    {
        -: 4150:	SV *	RETVAL;
        -: 4151:#line 3734 "LikeR.xs"
        -: 4152:	{
        -: 4153:		SV*restrict x_sv = NULL;
        -: 4154:		SV*restrict y_sv = NULL;
        -: 4155:		double mu = 0.0, conf_level = 0.95;
        -: 4156:		bool paired = FALSE, var_equal = FALSE;
        -: 4157:		const char*restrict alternative = "two.sided";
        -: 4158:
        -: 4159:		int arg_idx = 0;
        -: 4160:
        -: 4161:		// 1. Shift first positional argument as 'x' if it's an array reference
        -: 4162:		if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 4163:		  x_sv = ST(arg_idx);
        -: 4164:		  arg_idx++;
        -: 4165:		}
        -: 4166:
        -: 4167:		// 2. Shift second positional argument as 'y' if it's an array reference
        -: 4168:		if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 4169:		  y_sv = ST(arg_idx);
        -: 4170:		  arg_idx++;
        -: 4171:		}
        -: 4172:
        -: 4173:		// Ensure the remaining arguments form complete key-value pairs
        -: 4174:		if ((items - arg_idx) % 2 != 0) {
        -: 4175:		  croak("Usage: t_test(\\@x, [\\@y], key => value, ...)");
        -: 4176:		}
        -: 4177:
        -: 4178:		// --- Parse named arguments from the remaining flat stack ---
        -: 4179:		for (; arg_idx < items; arg_idx += 2) {
        -: 4180:			const char*restrict key = SvPV_nolen(ST(arg_idx));
        -: 4181:			SV*restrict val = ST(arg_idx + 1);
        -: 4182:
        -: 4183:			if      (strEQ(key, "x"))           x_sv        = val;
        -: 4184:			else if (strEQ(key, "y"))           y_sv        = val;
        -: 4185:			else if (strEQ(key, "mu"))          mu          = SvNV(val);
        -: 4186:			else if (strEQ(key, "paired"))      paired      = SvTRUE(val);
        -: 4187:			else if (strEQ(key, "var_equal"))   var_equal   = SvTRUE(val);
        -: 4188:			else if (strEQ(key, "conf_level"))  conf_level  = SvNV(val);
        -: 4189:			else if (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 4190:			else croak("t_test: unknown argument '%s'", key);
        -: 4191:		}
        -: 4192:
        -: 4193:		// --- Validate required / types ---
        -: 4194:		if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 4195:			croak("t_test: 'x' is a required argument and must be an ARRAY reference");
        -: 4196:		AV*restrict x_av = (AV*)SvRV(x_sv);
        -: 4197:		size_t nx = av_len(x_av) + 1;
        -: 4198:		if (nx < 2) croak("t_test: 'x' needs at least 2 elements");
        -: 4199:		AV*restrict y_av = NULL;
        -: 4200:		if (y_sv && SvROK(y_sv) && SvTYPE(SvRV(y_sv)) == SVt_PVAV)
        -: 4201:			y_av = (AV*)SvRV(y_sv);
        -: 4202:
        -: 4203:		if (conf_level <= 0.0 || conf_level >= 1.0)
        -: 4204:			croak("t_test: 'conf_level' must be between 0 and 1");
        -: 4205:		// --- Computation via Welford's Algorithm --- */
        -: 4206:		double mean_x = 0.0, M2_x = 0.0, var_x, t_stat, df, p_val, std_err, cint_est;
        -: 4207:		HV*restrict results = newHV();
        -: 4208:		for (size_t i = 0; i < nx; i++) {
        -: 4209:			SV**restrict tv = av_fetch(x_av, i, 0);
        -: 4210:			double val = (tv && SvOK(*tv)) ? SvNV(*tv) : 0;
        -: 4211:			double delta = val - mean_x;
        -: 4212:			mean_x += delta / (i + 1);
        -: 4213:			M2_x += delta * (val - mean_x);
        -: 4214:		}
        -: 4215:		var_x = M2_x / (nx - 1);
        -: 4216:		if (var_x == 0.0 && !y_av) croak("t_test: data are essentially constant");
        -: 4217:
        -: 4218:		if (paired || y_av) {
        -: 4219:			if (!y_av) croak("t_test: 'y' must be provided for paired or two-sample tests");
        -: 4220:			size_t ny = av_len(y_av) + 1;
        -: 4221:			if (paired && ny != nx) croak("t_test: Paired arrays must be same length");
        -: 4222:			double mean_y = 0.0, M2_y = 0.0, var_y;
        -: 4223:			for (size_t i = 0; i < ny; i++) {
        -: 4224:				 SV**restrict tv = av_fetch(y_av, i, 0);
        -: 4225:				 double val = (tv && SvOK(*tv)) ? SvNV(*tv) : 0;
        -: 4226:				 double delta = val - mean_y;
        -: 4227:				 mean_y += delta / (i + 1);
        -: 4228:				 M2_y += delta * (val - mean_y);
        -: 4229:			}
        -: 4230:			var_y = M2_y / (ny - 1);
        -: 4231:			if (paired) {
        -: 4232:				 double mean_d = 0.0, M2_d = 0.0;
        -: 4233:				 for (size_t i = 0; i < nx; i++) {
        -: 4234:					  SV**restrict dx_ptr = av_fetch(x_av, i, 0);
        -: 4235:					  SV**restrict dy_ptr = av_fetch(y_av, i, 0);
        -: 4236:				     double dx = (dx_ptr && SvOK(*dx_ptr)) ? SvNV(*dx_ptr) : 0.0;
        -: 4237:				     double dy = (dy_ptr && SvOK(*dy_ptr)) ? SvNV(*dy_ptr) : 0.0;
        -: 4238:				     double val = dx - dy;
        -: 4239:				     double delta = val - mean_d;
        -: 4240:				     mean_d += delta / (i + 1);
        -: 4241:				     M2_d += delta * (val - mean_d);
        -: 4242:				 }
        -: 4243:				 double var_d = M2_d / (nx - 1);
        -: 4244:				 if (var_d == 0.0) croak("t_test: data are essentially constant");
        -: 4245:				 cint_est = mean_d;
        -: 4246:				 std_err  = sqrt(var_d / nx);
        -: 4247:				 t_stat   = (cint_est - mu) / std_err;
        -: 4248:				 df       = nx - 1;
        -: 4249:				 hv_store(results, "estimate", 8, newSVnv(mean_d), 0);
        -: 4250:			} else if (var_equal) {
        -: 4251:				 if (var_x == 0.0 && var_y == 0.0) croak("t_test: data are essentially constant");
        -: 4252:				 double pooled_var = ((nx - 1) * var_x + (ny - 1) * var_y) / (nx + ny - 2);
        -: 4253:				 cint_est = mean_x - mean_y;
        -: 4254:				 std_err  = sqrt(pooled_var * (1.0 / nx + 1.0 / ny));
        -: 4255:				 t_stat   = (cint_est - mu) / std_err;
        -: 4256:				 df       = nx + ny - 2;
        -: 4257:				 hv_store(results, "estimate_x", 10, newSVnv(mean_x), 0);
        -: 4258:				 hv_store(results, "estimate_y", 10, newSVnv(mean_y), 0);
        -: 4259:			} else {
        -: 4260:				 if (var_x == 0.0 && var_y == 0.0) croak("t_test: data are essentially constant");
        -: 4261:				 cint_est         = mean_x - mean_y;
        -: 4262:				 double stderr_x2 = var_x / nx;
        -: 4263:				 double stderr_y2 = var_y / ny;
        -: 4264:				 std_err          = sqrt(stderr_x2 + stderr_y2);
        -: 4265:				 t_stat           = (cint_est - mu) / std_err;
        -: 4266:				 df = pow(stderr_x2 + stderr_y2, 2) /
        -: 4267:				      (pow(stderr_x2, 2) / (nx - 1) + pow(stderr_y2, 2) / (ny - 1));
        -: 4268:				 hv_store(results, "estimate_x", 10, newSVnv(mean_x), 0);
        -: 4269:				 hv_store(results, "estimate_y", 10, newSVnv(mean_y), 0);
        -: 4270:			}
        -: 4271:		} else {
        -: 4272:			cint_est = mean_x;
        -: 4273:			std_err  = sqrt(var_x / nx);
        -: 4274:			t_stat   = (cint_est - mu) / std_err;
        -: 4275:			df       = nx - 1;
        -: 4276:			hv_store(results, "estimate", 8, newSVnv(mean_x), 0);
        -: 4277:		}
        -: 4278:		p_val = get_t_pvalue(t_stat, df, alternative);
        -: 4279:		double alpha = 1.0 - conf_level, t_crit, ci_lower, ci_upper;
        -: 4280:		if (strcmp(alternative, "less") == 0) {
        -: 4281:			t_crit   = qt_tail(df, alpha);
        -: 4282:			ci_lower = -INFINITY;
        -: 4283:			ci_upper = cint_est + t_crit * std_err;
        -: 4284:		} else if (strcmp(alternative, "greater") == 0) {
        -: 4285:			t_crit   = qt_tail(df, alpha);
        -: 4286:			ci_lower = cint_est - t_crit * std_err;
        -: 4287:			ci_upper = INFINITY;
        -: 4288:		} else {
        -: 4289:			t_crit   = qt_tail(df, alpha / 2.0);
        -: 4290:			ci_lower = cint_est - t_crit * std_err;
        -: 4291:			ci_upper = cint_est + t_crit * std_err;
        -: 4292:		}
        -: 4293:		AV*restrict conf_int = newAV();
        -: 4294:		av_push(conf_int, newSVnv(ci_lower));
        -: 4295:		av_push(conf_int, newSVnv(ci_upper));
        -: 4296:		hv_store(results, "statistic", 9, newSVnv(t_stat), 0);
        -: 4297:		hv_store(results, "df",        2, newSVnv(df),     0);
        -: 4298:		hv_store(results, "p_value",   7, newSVnv(p_val),  0);
        -: 4299:		hv_store(results, "conf_int",  8, newRV_noinc((SV*)conf_int), 0);
        -: 4300:		RETVAL = newRV_noinc((SV*)results);
        -: 4301:	}
        -: 4302:#line 4303 "LikeR.c"
      126: 4303:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 126
      126: 4304:	ST(0) = RETVAL;
        -: 4305:    }
      126: 4306:    XSRETURN(1);
        -: 4307:}
        -: 4308:
        -: 4309:
        -: 4310:XS_EUPXS(XS_Stats__LikeR_p_adjust); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_p_adjust called 45 returned 87% blocks executed 90%
       45: 4311:XS_EUPXS(XS_Stats__LikeR_p_adjust)
        -: 4312:{
       45: 4313:    dVAR; dXSARGS;
       45: 4313-block  0
call    0 returned 45
       45: 4314:    if (items < 1 || items > 2)
branch  0 taken 45 (fallthrough)
branch  1 taken 0
       45: 4314-block  0
branch  2 taken 0 (fallthrough)
branch  3 taken 45
    #####: 4315:       croak_xs_usage(cv,  "p_sv, method= \"holm\"");
    %%%%%: 4315-block  0
call    0 never executed
        -: 4316:    PERL_UNUSED_VAR(ax); /* -Wall */
       45: 4317:    SP -= items;
        -: 4318:    {
       45: 4319:	SV *	p_sv = ST(0)
        -: 4320:;
        -: 4321:	const char *	method;
        -: 4322:
       45: 4323:	if (items < 2)
       45: 4323-block  0
branch  0 taken 6 (fallthrough)
branch  1 taken 39
        6: 4324:	    method = "holm";
        6: 4324-block  0
        -: 4325:	else {
       39: 4326:	    method = (const char *)SvPV_nolen(ST(1))
       39: 4326-block  0
call    0 returned 39
        -: 4327:;
        -: 4328:	}
        -: 4329:#line 3889 "LikeR.xs"
        -: 4330:		if (!SvROK(p_sv) || SvTYPE(SvRV(p_sv)) != SVt_PVAV) {
        -: 4331:			croak("p_adjust: first argument must be an ARRAY reference of p-values");
        -: 4332:		}
        -: 4333:		AV *restrict p_av = (AV*)SvRV(p_sv);
        -: 4334:		size_t n = av_len(p_av) + 1;
        -: 4335:		// Handle empty input
        -: 4336:		if (n == 0) {
        -: 4337:			XSRETURN_EMPTY;
        -: 4338:		}
        -: 4339:		// Normalize method string
        -: 4340:		char meth[64];
        -: 4341:		strncpy(meth, method, 63); meth[63] = '\0';
        -: 4342:		for(unsigned short int i = 0; meth[i]; i++) meth[i] = tolower(meth[i]);
        -: 4343:		// Resolve aliases
        -: 4344:		if (strstr(meth, "benjamini") && strstr(meth, "hochberg")) strcpy(meth, "bh");
        -: 4345:		if (strstr(meth, "benjamini") && strstr(meth, "yekutieli")) strcpy(meth, "by");
        -: 4346:		if (strcmp(meth, "fdr") == 0) strcpy(meth, "bh");
        -: 4347:		// Allocate C memory
        -: 4348:		PVal *restrict arr;
        -: 4349:		double *restrict adj;
        -: 4350:		Newx(arr, n, PVal);
        -: 4351:		Newx(adj, n, double);
        -: 4352:
        -: 4353:		for (size_t i = 0; i < n; i++) {
        -: 4354:			SV**restrict tv = av_fetch(p_av, i, 0);
        -: 4355:			arr[i].p = (tv && SvOK(*tv)) ? SvNV(*tv) : 1.0;
        -: 4356:			arr[i].orig_idx = i;
        -: 4357:		}
        -: 4358:		// Sort ascending (Stable sort using original index)
        -: 4359:		qsort(arr, n, sizeof(PVal), cmp_pval);
        -: 4360:#line 4361 "LikeR.c"
        -: 4361:#line 3920 "LikeR.xs"
        -: 4362:		if (strcmp(meth, "bonferroni") == 0) {
        -: 4363:			for (size_t i = 0; i < n; i++) {
        -: 4364:				double v = arr[i].p * n;
        -: 4365:				adj[arr[i].orig_idx] = (v < 1.0) ? v : 1.0;
        -: 4366:			}
        -: 4367:		} else if (strcmp(meth, "holm") == 0) {
        -: 4368:			double cummax = 0.0;
        -: 4369:			for (size_t i = 0; i < n; i++) {
        -: 4370:				 double v = arr[i].p * (n - i);
        -: 4371:				 if (v > cummax) cummax = v;
        -: 4372:				 adj[arr[i].orig_idx] = (cummax < 1.0) ? cummax : 1.0;
        -: 4373:			}
        -: 4374:		} else if (strcmp(meth, "hochberg") == 0) {
        -: 4375:			double cummin = 1.0;
        -: 4376:			for (ssize_t i = n - 1; i >= 0; i--) {
        -: 4377:				 double v = arr[i].p * (n - i);
        -: 4378:				 if (v < cummin) cummin = v;
        -: 4379:				 adj[arr[i].orig_idx] = (cummin < 1.0) ? cummin : 1.0;
        -: 4380:			}
        -: 4381:		} else if (strcmp(meth, "bh") == 0) {
        -: 4382:			double cummin = 1.0;
        -: 4383:			for (ssize_t i = n - 1; i >= 0; i--) {
        -: 4384:				double v = arr[i].p * n / (i + 1.0);
        -: 4385:				if (v < cummin) cummin = v;
        -: 4386:				adj[arr[i].orig_idx] = (cummin < 1.0) ? cummin : 1.0;
        -: 4387:			}
        -: 4388:		} else if (strcmp(meth, "by") == 0) {
        -: 4389:			double q = 0.0;
        -: 4390:			for (size_t i = 1; i <= n; i++) q += 1.0 / i;
        -: 4391:			double cummin = 1.0;
        -: 4392:			for (ssize_t i = n - 1; i >= 0; i--) {
        -: 4393:				double v = arr[i].p * n / (i + 1.0) * q;
        -: 4394:				if (v < cummin) cummin = v;
        -: 4395:				adj[arr[i].orig_idx] = (cummin < 1.0) ? cummin : 1.0;
        -: 4396:			}
        -: 4397:		} else if (strcmp(meth, "hommel") == 0) {
        -: 4398:			double *restrict pa, *restrict q_arr;
        -: 4399:			Newx(pa, n, double);
        -: 4400:			Newx(q_arr, n, double);
        -: 4401:			// Initial: min(n * p[i] / (i + 1))
        -: 4402:			double min_val = n * arr[0].p;
        -: 4403:			for (size_t i = 1; i < n; i++) {
        -: 4404:				double temp = (n * arr[i].p) / (i + 1.0);
        -: 4405:				if (temp < min_val) {
        -: 4406:				   min_val = temp;
        -: 4407:				}
        -: 4408:			}
        -: 4409:			// pa <- q <- rep(min, n)
        -: 4410:			for (size_t i = 0; i < n; i++) {
        -: 4411:				 pa[i] = min_val;
        -: 4412:				 q_arr[i] = min_val;
        -: 4413:			}
        -: 4414:			for (size_t j = n - 1; j >= 2; j--) {
        -: 4415:				 ssize_t n_mj = n - j;       // Max index for 'ij'. Length is n_mj + 1
        -: 4416:				 ssize_t i2_len = j - 1;     // Length of 'i2
        -: 4417:				 // Calculate q1 = min(j * p[i2] / (2:j))
        -: 4418:				 double q1 = (j * arr[n_mj + 1].p) / 2.0;
        -: 4419:				 for (size_t k = 1; k < i2_len; k++) {
        -: 4420:				     double temp_q1 = (j * arr[n_mj + 1 + k].p) / (2.0 + k);
        -: 4421:				     if (temp_q1 < q1) {
        -: 4422:				         q1 = temp_q1;
        -: 4423:				     }
        -: 4424:				 }
        -: 4425:				 // q[ij] <- pmin(j * p[ij], q1)
        -: 4426:				 for (size_t i = 0; i <= n_mj; i++) {
        -: 4427:				     double v = j * arr[i].p;
        -: 4428:				     q_arr[i] = (v < q1) ? v : q1;
        -: 4429:				 }
        -: 4430:				 // q[i2] <- q[n - j]
        -: 4431:				 for (size_t i = 0; i < i2_len; i++) {
        -: 4432:				     q_arr[n_mj + 1 + i] = q_arr[n_mj];
        -: 4433:				}
        -: 4434:				 // pa <- pmax(pa, q)
        -: 4435:				for (size_t i = 0; i < n; i++) {
        -: 4436:				    if (pa[i] < q_arr[i]) {
        -: 4437:				       pa[i] = q_arr[i];
        -: 4438:				    }
        -: 4439:				}
        -: 4440:			}
        -: 4441:			// pmin(1, pmax(pa, p))[ro] — map sorted results back to original indices
        -: 4442:			for (size_t i = 0; i < n; i++) {
        -: 4443:				double v = (pa[i] > arr[i].p) ? pa[i] : arr[i].p;
        -: 4444:				if (v > 1.0) v = 1.0;
        -: 4445:				adj[arr[i].orig_idx] = v;
        -: 4446:			}
        -: 4447:			Safefree(pa);  Safefree(q_arr);
        -: 4448:		} else if (strcmp(meth, "none") == 0) {
        -: 4449:			for (size_t i = 0; i < n; i++) {
        -: 4450:				adj[arr[i].orig_idx] = arr[i].p;
        -: 4451:			}
        -: 4452:		} else {
        -: 4453:			Safefree(arr); Safefree(adj);
        -: 4454:			croak("Unknown p-value adjustment method: %s", method);
        -: 4455:		}
        -: 4456:		// Push values onto the Perl stack as a flat list
        -: 4457:		EXTEND(SP, n);
        -: 4458:		for (size_t i = 0; i < n; i++) {
        -: 4459:			PUSHs(sv_2mortal(newSVnv(adj[i])));
        -: 4460:		}
        -: 4461:		Safefree(arr); arr = NULL;
        -: 4462:		Safefree(adj); adj = NULL;
        -: 4463:#line 4464 "LikeR.c"
       36: 4464:	PUTBACK;
       36: 4465:	return;
        -: 4466:    }
        -: 4467:}
        -: 4468:
        -: 4469:
        -: 4470:XS_EUPXS(XS_Stats__LikeR_median); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_median called 18 returned 83% blocks executed 96%
       18: 4471:XS_EUPXS(XS_Stats__LikeR_median)
        -: 4472:{
       18: 4473:    dVAR; dXSARGS;
       18: 4473-block  0
call    0 returned 18
        -: 4474:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4475:    PERL_UNUSED_VAR(items); /* -W */
        -: 4476:    {
        -: 4477:	double	RETVAL;
      18*: 4478:	dXSTARG;
branch  0 taken 18 (fallthrough)
branch  1 taken 0
       18: 4478-block  0
    %%%%%: 4478-block  1
call    2 never executed
       18: 4478-block  2
        -: 4479:#line 4025 "LikeR.xs"
        -: 4480:		size_t total_count = 0, k = 0;
        -: 4481:		double *restrict nums;
        -: 4482:		double median_val = 0.0;
        -: 4483:#line 4484 "LikeR.c"
        -: 4484:#line 4029 "LikeR.xs"
        -: 4485:	  // Pass 1: Count total valid elements to allocate memory
        -: 4486:	  for (size_t i = 0; i < items; i++) {
        -: 4487:		   SV*restrict arg = ST(i);
        -: 4488:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4489:		       AV*restrict av = (AV*)SvRV(arg);
        -: 4490:		       size_t len = av_len(av) + 1;
        -: 4491:		       for (size_t j = 0; j < len; j++) {
        -: 4492:		           SV**restrict tv = av_fetch(av, j, 0);
        -: 4493:		           if (tv && SvOK(*tv)) { total_count++; }
        -: 4494:		       }
        -: 4495:		   } else if (SvOK(arg)) {
        -: 4496:		       total_count++;
        -: 4497:		   }
        -: 4498:	  }
        -: 4499:	  if (total_count == 0) croak("median needs >= 1 element");
        -: 4500:	  // Allocate C array now that we know the exact size
        -: 4501:	  Newx(nums, total_count, double);
        -: 4502:	  // Pass 2: Populate the C array
        -: 4503:	  for (size_t i = 0; i < items; i++) {
        -: 4504:		   SV*restrict arg = ST(i);
        -: 4505:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4506:		       AV*restrict av = (AV*)SvRV(arg);
        -: 4507:		       size_t len = av_len(av) + 1;
        -: 4508:		       for (size_t j = 0; j < len; j++) {
        -: 4509:		           SV**restrict tv = av_fetch(av, j, 0);
        -: 4510:		           if (tv && SvOK(*tv)) { 
        -: 4511:		               nums[k++] = SvNV(*tv); 
        -: 4512:		           }
        -: 4513:		       }
        -: 4514:		   } else if (SvOK(arg)) {
        -: 4515:		       nums[k++] = SvNV(arg);
        -: 4516:		   }
        -: 4517:	  }
        -: 4518:	  // Sort and calculate median
        -: 4519:	  qsort(nums, total_count, sizeof(double), compare_doubles);
        -: 4520:	  if (total_count % 2 == 0) {
        -: 4521:		   median_val = (nums[total_count / 2 - 1] + nums[total_count / 2]) / 2.0;
        -: 4522:	  } else {
        -: 4523:		   median_val = nums[total_count / 2];
        -: 4524:	  }
        -: 4525:	  Safefree(nums); nums = NULL;
        -: 4526:	  RETVAL = median_val;
        -: 4527:#line 4528 "LikeR.c"
       15: 4528:	TARGn((double)RETVAL, 1);
branch  0 taken 6 (fallthrough)
branch  1 taken 9
        6: 4528-block  0
        9: 4528-block  1
call    2 returned 9
       15: 4529:	ST(0) = TARG;
        -: 4530:    }
       15: 4531:    XSRETURN(1);
       15: 4531-block  0
        -: 4532:}
        -: 4533:
        -: 4534:
        -: 4535:XS_EUPXS(XS_Stats__LikeR_cor); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_cor called 18 returned 67% blocks executed 75%
       18: 4536:XS_EUPXS(XS_Stats__LikeR_cor)
        -: 4537:{
       18: 4538:    dVAR; dXSARGS;
       18: 4538-block  0
call    0 returned 18
       18: 4539:    if (items < 1 || items > 3)
branch  0 taken 18 (fallthrough)
branch  1 taken 0
       18: 4539-block  0
branch  2 taken 0 (fallthrough)
branch  3 taken 18
    #####: 4540:       croak_xs_usage(cv,  "x_sv, y_sv= &PL_sv_undef, method= \"pearson\"");
    %%%%%: 4540-block  0
call    0 never executed
        -: 4541:    {
        -: 4542:	SV *	RETVAL;
       18: 4543:	SV *	x_sv = ST(0)
        -: 4544:;
        -: 4545:	SV *	y_sv;
        -: 4546:	const char *	method;
        -: 4547:
       18: 4548:	if (items < 2)
       18: 4548-block  0
branch  0 taken 0 (fallthrough)
branch  1 taken 18
    #####: 4549:	    y_sv = &PL_sv_undef;
    %%%%%: 4549-block  0
        -: 4550:	else {
       18: 4551:	    y_sv = ST(1)
       18: 4551-block  0
        -: 4552:;
        -: 4553:	}
        -: 4554:
       18: 4555:	if (items < 3)
       18: 4555-block  0
branch  0 taken 3 (fallthrough)
branch  1 taken 15
        3: 4556:	    method = "pearson";
        3: 4556-block  0
        -: 4557:	else {
       15: 4558:	    method = (const char *)SvPV_nolen(ST(2))
       15: 4558-block  0
call    0 returned 15
        -: 4559:;
        -: 4560:	}
        -: 4561:#line 4076 "LikeR.xs"
        -: 4562:	  // --- validate method -------------------------------------------
        -: 4563:	  if (strcmp(method, "pearson")  != 0 &&
        -: 4564:		   strcmp(method, "spearman") != 0 &&
        -: 4565:		   strcmp(method, "kendall")  != 0)
        -: 4566:		   croak("cor: unknown method '%s' (use 'pearson', 'spearman', or 'kendall')",
        -: 4567:		         method);
        -: 4568:
        -: 4569:	  // --- validate x ------------------------------------------------
        -: 4570:	  if (!SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 4571:		   croak("cor: x must be an ARRAY reference");
        -: 4572:
        -: 4573:	  AV*restrict x_av = (AV*)SvRV(x_sv);
        -: 4574:	  size_t nx   = av_len(x_av) + 1;
        -: 4575:	  if (nx == 0) croak("cor: x is empty");
        -: 4576:
        -: 4577:	  // --- detect whether x is a flat vector or a matrix (AoA) -------
        -: 4578:	  bool x_is_matrix = 0;
        -: 4579:	  {
        -: 4580:		   SV**restrict fp = av_fetch(x_av, 0, 0);
        -: 4581:		   if (fp && SvROK(*fp) && SvTYPE(SvRV(*fp)) == SVt_PVAV)
        -: 4582:		       x_is_matrix = 1;
        -: 4583:	  }
        -: 4584:
        -: 4585:	  // --- detect y ----------------------------
        -: 4586:	  bool has_y = (SvOK(y_sv) && SvROK(y_sv) &&
        -: 4587:		            SvTYPE(SvRV(y_sv)) == SVt_PVAV);
        -: 4588:
        -: 4589:	  AV*restrict y_av = has_y ? (AV*)SvRV(y_sv) : NULL;
        -: 4590:	  size_t ny = has_y ? av_len(y_av) + 1 : 0;
        -: 4591:
        -: 4592:	  bool y_is_matrix = 0;
        -: 4593:	  if (has_y && ny > 0) {
        -: 4594:		   SV**restrict fp = av_fetch(y_av, 0, 0);
        -: 4595:		   if (fp && SvROK(*fp) && SvTYPE(SvRV(*fp)) == SVt_PVAV)
        -: 4596:		       y_is_matrix = 1;
        -: 4597:	  }
        -: 4598:
        -: 4599:#line 4600 "LikeR.c"
        -: 4600:#line 4114 "LikeR.xs"
        -: 4601:      // Branch 1: both inputs are flat vectors  →  scalar result
        -: 4602:	  if (!x_is_matrix && !y_is_matrix) {
        -: 4603:		   if (!has_y) {
        -: 4604:		       /* cor(vector) == 1 by definition */
        -: 4605:		       RETVAL = newSVnv(1.0);
        -: 4606:		   } else {
        -: 4607:		       if (nx != ny)
        -: 4608:		           croak("cor: x and y must have the same length (%lu vs %lu)",
        -: 4609:		                 nx, ny);
        -: 4610:
        -: 4611:		       if (nx < 2)
        -: 4612:		           croak("cor: need at least 2 observations");
        -: 4613:
        -: 4614:		       double *restrict xd, *restrict yd;
        -: 4615:		       Newx(xd, nx, double);
        -: 4616:		       Newx(yd, ny, double);
        -: 4617:
        -: 4618:		       for (size_t i = 0; i < nx; i++) {
        -: 4619:		           SV**restrict tv = av_fetch(x_av, i, 0);
        -: 4620:		           xd[i] = (tv && SvOK(*tv) && looks_like_number(*tv)) ? SvNV(*tv) : NAN;
        -: 4621:		       }
        -: 4622:		       for (size_t i = 0; i < ny; i++) {
        -: 4623:		           SV**restrict tv = av_fetch(y_av, i, 0);
        -: 4624:		           yd[i] = (tv && SvOK(*tv) && looks_like_number(*tv)) ? SvNV(*tv) : NAN;
        -: 4625:		       }
        -: 4626:
        -: 4627:		       double r = compute_cor(xd, yd, nx, method);
        -: 4628:		       Safefree(xd); Safefree(yd);
        -: 4629:		       RETVAL = newSVnv(r);
        -: 4630:		   }
        -: 4631:	  } else {//Branch 2: x is a matrix (or y is a matrix)  →  AoA result
        -: 4632:		   // -- resolve x matrix dimensions
        -: 4633:		   if (!x_is_matrix)
        -: 4634:		       croak("cor: x must be a matrix (array ref of array refs) "
        -: 4635:		             "when y is a matrix");
        -: 4636:
        -: 4637:		   SV**restrict xr0 = av_fetch(x_av, 0, 0);
        -: 4638:		   if (!xr0 || !SvROK(*xr0) || SvTYPE(SvRV(*xr0)) != SVt_PVAV)
        -: 4639:		       croak("cor: each row of x must be an ARRAY reference");
        -: 4640:
        -: 4641:		   size_t ncols_x = av_len((AV*)SvRV(*xr0)) + 1;
        -: 4642:		   if (ncols_x == 0) croak("cor: x matrix has zero columns");
        -: 4643:
        -: 4644:		   size_t nrows   = nx;   /* observations */
        -: 4645:
        -: 4646:		   // PRE-VALIDATION PASS: Ensure all rows are arrays to prevent memory leaks on croak
        -: 4647:		   for (size_t i = 0; i < nrows; i++) {
        -: 4648:		       SV**restrict rv = av_fetch(x_av, i, 0);
        -: 4649:		       if (!rv || !SvROK(*rv) || SvTYPE(SvRV(*rv)) != SVt_PVAV)
        -: 4650:		           croak("cor: x row %lu is not an array ref", i);
        -: 4651:		   }
        -: 4652:
        -: 4653:           if (has_y && y_is_matrix) {
        -: 4654:		       if (ny != nrows) croak("cor: x and y must have the same number of rows (%lu vs %lu)", nrows, ny);
        -: 4655:               for (size_t i = 0; i < nrows; i++) {
        -: 4656:		           SV**restrict rv = av_fetch(y_av, i, 0);
        -: 4657:		           if (!rv || !SvROK(*rv) || SvTYPE(SvRV(*rv)) != SVt_PVAV)
        -: 4658:		               croak("cor: y row %lu is not an array ref", i);
        -: 4659:		       }
        -: 4660:           }
        -: 4661:
        -: 4662:		   // -- extract x columns
        -: 4663:		   double **restrict col_x;
        -: 4664:		   Newx(col_x, ncols_x, double*);
        -: 4665:
        -: 4666:		   for (size_t j = 0; j < ncols_x; j++) {
        -: 4667:		       Newx(col_x[j], nrows, double);
        -: 4668:		       for (size_t i = 0; i < nrows; i++) {
        -: 4669:		           SV**restrict rv = av_fetch(x_av, i, 0);
        -: 4670:		           AV*restrict  row = (AV*)SvRV(*rv);
        -: 4671:		           SV**restrict cv  = av_fetch(row, j, 0);
        -: 4672:		           col_x[j][i] = (cv && SvOK(*cv) && looks_like_number(*cv)) ? SvNV(*cv) : NAN;
        -: 4673:		       }
        -: 4674:		   }
        -: 4675:
        -: 4676:		   // -- resolve y: separate matrix or re-use x (symmetric)
        -: 4677:		   size_t ncols_y;
        -: 4678:		   double **restrict col_y   = NULL;
        -: 4679:		   bool symmetric = 0;
        -: 4680:
        -: 4681:		   // 1 = cor(X) — result is symmetric
        -: 4682:		   if (has_y && y_is_matrix) {
        -: 4683:		       // cross-correlation: X (nrows × p) vs Y (nrows × q)
        -: 4684:		       SV**restrict yr0 = av_fetch(y_av, 0, 0);
        -: 4685:		       ncols_y = av_len((AV*)SvRV(*yr0)) + 1;
        -: 4686:		       if (ncols_y == 0) croak("cor: y matrix has zero columns");
        -: 4687:
        -: 4688:		       Newx(col_y, ncols_y, double*);
        -: 4689:		       for (size_t j = 0; j < ncols_y; j++) {
        -: 4690:		           Newx(col_y[j], nrows, double);
        -: 4691:		           for (size_t i = 0; i < nrows; i++) {
        -: 4692:		               SV**restrict  rv = av_fetch(y_av, i, 0);
        -: 4693:		               AV*restrict  row = (AV*)SvRV(*rv);
        -: 4694:		               SV**restrict cv  = av_fetch(row, j, 0);
        -: 4695:		               col_y[j][i] = (cv && SvOK(*cv) && looks_like_number(*cv)) ? SvNV(*cv) : NAN;
        -: 4696:		           }
        -: 4697:		       }
        -: 4698:		   } else { // cor(X) — symmetric p×p result; share column arrays
        -: 4699:		       ncols_y  = ncols_x;
        -: 4700:		       col_y    = col_x;
        -: 4701:		       symmetric = 1;
        -: 4702:		   }
        -: 4703:		   if (nrows < 2)
        -: 4704:		       croak("cor: need at least 2 observations (got %lu)", nrows);
        -: 4705:		   // -- build cache for symmetric case: compute upper triangle, store results, mirror to lower triangle
        -: 4706:		   AV*restrict result_av = newAV();
        -: 4707:		   av_extend(result_av, ncols_x - 1);
        -: 4708:		   // Allocate per-row AVs up front so we can fill them in order
        -: 4709:		   AV **restrict rows_out;
        -: 4710:		   Newx(rows_out, ncols_x, AV*);
        -: 4711:		   for (size_t i = 0; i < ncols_x; i++) {
        -: 4712:		       rows_out[i] = newAV();
        -: 4713:		       av_extend(rows_out[i], ncols_y - 1);
        -: 4714:		   }
        -: 4715:		   if (symmetric) {
        -: 4716:		       /* Upper triangle + diagonal, then mirror. r_cache[i][j] (j >= i) holds the computed value. */
        -: 4717:		       double **restrict r_cache;
        -: 4718:		       Newx(r_cache, ncols_x, double*);
        -: 4719:		       for (size_t i = 0; i < ncols_x; i++)
        -: 4720:		           Newx(r_cache[i], ncols_x, double);
        -: 4721:
        -: 4722:		       for (size_t i = 0; i < ncols_x; i++) {
        -: 4723:		           r_cache[i][i] = 1.0; // diagonal
        -: 4724:		           for (size_t j = i + 1; j < ncols_x; j++) {
        -: 4725:		               double r = compute_cor(col_x[i], col_x[j], nrows, method);
        -: 4726:		               r_cache[i][j] = r;
        -: 4727:		               r_cache[j][i] = r; // symmetry
        -: 4728:		           }
        -: 4729:		       }
        -: 4730:		       // fill output AoA from cache
        -: 4731:		       for (size_t i = 0; i < ncols_x; i++)
        -: 4732:		           for (size_t j = 0; j < ncols_x; j++)
        -: 4733:		               av_store(rows_out[i], j, newSVnv(r_cache[i][j]));
        -: 4734:
        -: 4735:		       for (size_t i = 0; i < ncols_x; i++) Safefree(r_cache[i]);
        -: 4736:		       Safefree(r_cache); r_cache = NULL;
        -: 4737:		   } else {
        -: 4738:		       // cross-correlation: every (i,j) pair is independent
        -: 4739:		       for (size_t i = 0; i < ncols_x; i++)
        -: 4740:		           for (size_t j = 0; j < ncols_y; j++)
        -: 4741:		               av_store(rows_out[i], j, newSVnv(compute_cor(col_x[i], col_y[j], nrows, method)));
        -: 4742:		   }
        -: 4743:		   // push row AVs into result
        -: 4744:		   for (size_t i = 0; i < ncols_x; i++)
        -: 4745:		       av_store(result_av, i, newRV_noinc((SV*)rows_out[i]));
        -: 4746:		   Safefree(rows_out); rows_out = NULL;
        -: 4747:		   // -- free column arrays -------------------------------------
        -: 4748:		   for (size_t j = 0; j < ncols_x; j++) Safefree(col_x[j]);
        -: 4749:		   Safefree(col_x); col_x = NULL;
        -: 4750:		   if (!symmetric) {
        -: 4751:		       for (size_t j = 0; j < ncols_y; j++) Safefree(col_y[j]);
        -: 4752:		       Safefree(col_y);
        -: 4753:		   }
        -: 4754:		   RETVAL = newRV_noinc((SV*)result_av);
        -: 4755:	  }
        -: 4756:#line 4757 "LikeR.c"
       12: 4757:	RETVAL = sv_2mortal(RETVAL);
       12: 4757-block  0
call    0 returned 12
       12: 4758:	ST(0) = RETVAL;
        -: 4759:    }
       12: 4760:    XSRETURN(1);
        -: 4761:}
        -: 4762:
        -: 4763:
        -: 4764:XS_EUPXS(XS_Stats__LikeR_scale); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_scale called 15 returned 80% blocks executed 71%
       15: 4765:XS_EUPXS(XS_Stats__LikeR_scale)
        -: 4766:{
       15: 4767:    dVAR; dXSARGS;
       15: 4767-block  0
call    0 returned 15
        -: 4768:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4769:    PERL_UNUSED_VAR(items); /* -W */
        -: 4770:    PERL_UNUSED_VAR(ax); /* -Wall */
       15: 4771:    SP -= items;
branch  0 taken 15 (fallthrough)
branch  1 taken 0
        -: 4772:    {
        -: 4773:#line 4275 "LikeR.xs"
        -: 4774:	{
        -: 4775:		bool do_center_mean = true, do_scale_sd = true;
        -: 4776:		double center_val = 0.0, scale_val = 1.0;
        -: 4777:		size_t data_items = items;
        -: 4778:		// 1. Parse Options Hash (if it exists as the last argument)
        -: 4779:		if (items > 0) {
        -: 4780:			SV*restrict last_arg = ST(items - 1);
        -: 4781:			if (SvROK(last_arg) && SvTYPE(SvRV(last_arg)) == SVt_PVHV) {
        -: 4782:				 data_items = items - 1; // Exclude hash from data processing
        -: 4783:				 HV*restrict opt_hv = (HV*)SvRV(last_arg);
        -: 4784:				 // --- Parse 'center'
        -: 4785:				 SV**restrict center_sv = hv_fetch(opt_hv, "center", 6, 0);
        -: 4786:				 if (center_sv) {
        -: 4787:				     SV*restrict val_sv = *center_sv;
        -: 4788:				     if (!SvOK(val_sv)) {
        -: 4789:				         do_center_mean = false; center_val = 0.0;
        -: 4790:				     } else {
        -: 4791:				         char *restrict str = SvPV_nolen(val_sv);
        -: 4792:				         /* Trap booleans and empty strings before numeric checks */
        -: 4793:				         if (strcasecmp(str, "mean") == 0 || strcasecmp(str, "true") == 0 || strcmp(str, "1") == 0) {
        -: 4794:				             do_center_mean = true;
        -: 4795:				         } else if (strcasecmp(str, "none") == 0 || strcasecmp(str, "false") == 0 || strcmp(str, "0") == 0 || strcmp(str, "") == 0) {
        -: 4796:				             do_center_mean = false; center_val = 0.0;
        -: 4797:				         } else if (looks_like_number(val_sv)) {
        -: 4798:				             do_center_mean = false; center_val = SvNV(val_sv);
        -: 4799:				         } else if (SvTRUE(val_sv)) {
        -: 4800:				             do_center_mean = true;
        -: 4801:				         } else {
        -: 4802:				             do_center_mean = false; center_val = 0.0;
        -: 4803:				         }
        -: 4804:				     }
        -: 4805:				 }
        -: 4806:				 // --- Parse 'scale' ---
        -: 4807:				 SV**restrict scale_sv = hv_fetch(opt_hv, "scale", 5, 0);
        -: 4808:				 if (scale_sv) {
        -: 4809:				     SV*restrict val_sv = *scale_sv;
        -: 4810:				     if (!SvOK(val_sv)) {
        -: 4811:				         do_scale_sd = false; scale_val = 1.0;
        -: 4812:				     } else {
        -: 4813:				         char *restrict str = SvPV_nolen(val_sv);
        -: 4814:				         if (strcasecmp(str, "sd") == 0 || strcasecmp(str, "true") == 0 || strcmp(str, "1") == 0) {
        -: 4815:				             do_scale_sd = true;
        -: 4816:				         } else if (strcasecmp(str, "none") == 0 || strcasecmp(str, "false") == 0 || strcmp(str, "0") == 0 || strcmp(str, "") == 0) {
        -: 4817:				             do_scale_sd = false; scale_val = 1.0;
        -: 4818:				         } else if (looks_like_number(val_sv)) {
        -: 4819:				             do_scale_sd = false; scale_val = SvNV(val_sv);
        -: 4820:				             if (scale_val == 0.0) scale_val = 1.0; /* Prevent Division By Zero */
        -: 4821:				         } else if (SvTRUE(val_sv)) {
        -: 4822:				             do_scale_sd = true;
        -: 4823:				         } else {
        -: 4824:				             do_scale_sd = false; scale_val = 1.0;
        -: 4825:				         }
        -: 4826:				     }
        -: 4827:				 }
        -: 4828:			}
        -: 4829:		}
        -: 4830:		// 2. Detect if the input is a Matrix (Array of Arrays)
        -: 4831:		bool is_matrix = false;
        -: 4832:		if (data_items == 1) {
        -: 4833:			SV*restrict first_arg = ST(0);
        -: 4834:			if (SvROK(first_arg) && SvTYPE(SvRV(first_arg)) == SVt_PVAV) {
        -: 4835:				 AV*restrict av = (AV*)SvRV(first_arg);
        -: 4836:				 if (av_len(av) >= 0) {
        -: 4837:				     SV**restrict first_elem = av_fetch(av, 0, 0);
        -: 4838:				     if (first_elem && SvROK(*first_elem) && SvTYPE(SvRV(*first_elem)) == SVt_PVAV) {
        -: 4839:				         is_matrix = true;
        -: 4840:				     }
        -: 4841:				 }
        -: 4842:			}
        -: 4843:		}
        -: 4844:		if (is_matrix) {
        -: 4845:			//=========================================================
        -: 4846:			// MATRIX MODE: Scale columns independently (Just like R)
        -: 4847:			//=========================================================
        -: 4848:			AV*restrict mat_av = (AV*)SvRV(ST(0));
        -: 4849:			size_t nrow = av_len(mat_av) + 1, ncol = 0;
        -: 4850:
        -: 4851:			SV**restrict first_row = av_fetch(mat_av, 0, 0);
        -: 4852:			ncol = av_len((AV*)SvRV(*first_row)) + 1;
        -: 4853:
        -: 4854:			if (nrow == 0 || ncol == 0) croak("scale requires non-empty matrix");
        -: 4855:
        -: 4856:			// Create a new matrix for the scaled output
        -: 4857:			AV*restrict result_av = newAV();
        -: 4858:			av_extend(result_av, nrow - 1);
        -: 4859:			AV**restrict row_ptrs = (AV**)safemalloc(nrow * sizeof(AV*));
        -: 4860:			for (size_t r = 0; r < nrow; r++) {
        -: 4861:				 row_ptrs[r] = newAV();
        -: 4862:				 av_extend(row_ptrs[r], ncol - 1);
        -: 4863:				 av_push(result_av, newRV_noinc((SV*)row_ptrs[r]));
        -: 4864:			}
        -: 4865:			// Calculate and apply scale per column
        -: 4866:			for (size_t c = 0; c < ncol; c++) {
        -: 4867:				 double col_sum = 0.0;
        -: 4868:				 double *restrict col_data;
        -: 4869:				 Newx(col_data, nrow, double);
        -: 4870:				 // Extract the column data
        -: 4871:				 for (size_t r = 0; r < nrow; r++) {
        -: 4872:				     SV**restrict row_sv = av_fetch(mat_av, r, 0);
        -: 4873:				     if (row_sv && SvROK(*row_sv)) {
        -: 4874:				         AV*restrict row_av = (AV*)SvRV(*row_sv);
        -: 4875:				         SV**restrict cell_sv = av_fetch(row_av, c, 0);
        -: 4876:				         col_data[r] = (cell_sv && SvOK(*cell_sv)) ? SvNV(*cell_sv) : 0.0;
        -: 4877:				     } else {
        -: 4878:				         col_data[r] = 0.0;
        -: 4879:				     }
        -: 4880:				     col_sum += col_data[r];
        -: 4881:				 }
        -: 4882:
        -: 4883:				 double col_center = do_center_mean ? (col_sum / nrow) : center_val;
        -: 4884:				 double col_scale = scale_val;
        -: 4885:				 // Calculate Standard Deviation for this specific column if needed
        -: 4886:				 if (do_scale_sd) {
        -: 4887:				     if (nrow <= 1) {
        -: 4888:				         Safefree(col_data);
        -: 4889:				         safefree(row_ptrs);
        -: 4890:				         croak("scale needs >= 2 rows to calculate standard deviation for a matrix column");
        -: 4891:				     }
        -: 4892:				     double sum_sq = 0.0;
        -: 4893:				     for (size_t r = 0; r < nrow; r++) {
        -: 4894:				         double diff = col_data[r] - col_center;
        -: 4895:				         sum_sq += diff * diff;
        -: 4896:				     }
        -: 4897:				     col_scale = sqrt(sum_sq / (nrow - 1));
        -: 4898:				 }
        -: 4899:				 // Store scaled values back into the new matrix rows
        -: 4900:				 for (size_t r = 0; r < nrow; r++) {
        -: 4901:				     double centered = col_data[r] - col_center;
        -: 4902:				     double final_val = (col_scale == 0.0) ? (0.0 / 0.0) : (centered / col_scale);
        -: 4903:				     av_store(row_ptrs[r], c, newSVnv(final_val));
        -: 4904:				 }
        -: 4905:				 Safefree(col_data);
        -: 4906:			}
        -: 4907:			safefree(row_ptrs);
        -: 4908:			// Push the resulting matrix as a single Reference onto the Perl stack
        -: 4909:			EXTEND(SP, 1);
        -: 4910:			PUSHs(sv_2mortal(newRV_noinc((SV*)result_av)));
        -: 4911:		} else {
        -: 4912:			// ======================================
        -: 4913:			// FLAT LIST MODE: Original functionality
        -: 4914:			// ======================================
        -: 4915:			size_t total_count = 0, k = 0;
        -: 4916:			double *restrict nums;
        -: 4917:			double sum = 0.0;
        -: 4918:			for (size_t i = 0; i < data_items; i++) {
        -: 4919:				SV*restrict arg = ST(i);
        -: 4920:				if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4921:				  AV*restrict av = (AV*)SvRV(arg);
        -: 4922:				  size_t len = av_len(av) + 1;
        -: 4923:				  for (unsigned int j = 0; j < len; j++) {
        -: 4924:						SV**restrict tv = av_fetch(av, j, 0);
        -: 4925:						if (tv && SvOK(*tv)) { total_count++; }
        -: 4926:				  }
        -: 4927:				} else if (SvOK(arg)) {
        -: 4928:				  total_count++;
        -: 4929:				}
        -: 4930:			}
        -: 4931:			if (total_count == 0) croak("scale requires at least 1 numeric element");
        -: 4932:			Newx(nums, total_count, double);
        -: 4933:			for (size_t i = 0; i < data_items; i++) {
        -: 4934:				 SV*restrict arg = ST(i);
        -: 4935:				 if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4936:				     AV*restrict av = (AV*)SvRV(arg);
        -: 4937:				     size_t len = av_len(av) + 1;
        -: 4938:				     for (size_t j = 0; j < len; j++) {
        -: 4939:				         SV**restrict tv = av_fetch(av, j, 0);
        -: 4940:				         if (tv && SvOK(*tv)) { 
        -: 4941:				             double val = SvNV(*tv);
        -: 4942:				             nums[k++] = val; sum += val;
        -: 4943:				         }
        -: 4944:				     }
        -: 4945:				 } else if (SvOK(arg)) {
        -: 4946:				     double val = SvNV(arg);
        -: 4947:				     nums[k++] = val; sum += val;
        -: 4948:				 }
        -: 4949:			}
        -: 4950:
        -: 4951:			if (do_center_mean) center_val = sum / total_count;
        -: 4952:
        -: 4953:			if (do_scale_sd) {
        -: 4954:				 if (total_count <= 1) {
        -: 4955:				     Safefree(nums);
        -: 4956:				     croak("scale needs >= 2 elements to calculate SD");
        -: 4957:				 }
        -: 4958:				 double sum_sq = 0.0;
        -: 4959:				 for (size_t i = 0; i < total_count; i++) {
        -: 4960:				     double diff = nums[i] - center_val;
        -: 4961:				     sum_sq += diff * diff;
        -: 4962:				 }
        -: 4963:				 scale_val = sqrt(sum_sq / (total_count - 1));
        -: 4964:			}
        -: 4965:			EXTEND(SP, total_count);
        -: 4966:			for (size_t i = 0; i < total_count; i++) {
        -: 4967:				double centered = nums[i] - center_val;
        -: 4968:				double final_val = (scale_val == 0.0) ? (0.0 / 0.0) : (centered / scale_val);
        -: 4969:				PUSHs(sv_2mortal(newSVnv(final_val)));
        -: 4970:			}
        -: 4971:			Safefree(nums); nums = NULL;
        -: 4972:		}
        -: 4973:	}
        -: 4974:#line 4975 "LikeR.c"
       12: 4975:	PUTBACK;
       12: 4976:	return;
       12: 4976-block  0
        -: 4977:    }
        -: 4978:}
        -: 4979:
        -: 4980:
        -: 4981:XS_EUPXS(XS_Stats__LikeR_matrix); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_matrix called 12 returned 25% blocks executed 82%
       12: 4982:XS_EUPXS(XS_Stats__LikeR_matrix)
        -: 4983:{
       12: 4984:    dVAR; dXSARGS;
       12: 4984-block  0
call    0 returned 12
branch  1 taken 0 (fallthrough)
branch  2 taken 12
        -: 4985:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4986:    PERL_UNUSED_VAR(items); /* -W */
        -: 4987:    {
        -: 4988:	SV *	RETVAL;
        -: 4989:#line 4478 "LikeR.xs"
        -: 4990:	// Basic check: must have an even number of arguments for key => value
        -: 4991:	if (items % 2 != 0) {
        -: 4992:	  croak("Usage: matrix(data => [...], nrow => $n, ncol => $m, byrow => $bool)");
        -: 4993:	}
        -: 4994:	SV*restrict data_sv = NULL;
        -: 4995:	size_t nrow = 0, ncol = 0;
        -: 4996:	bool byrow = FALSE, nrow_set = FALSE, ncol_set = FALSE;
        -: 4997:	// Parse named arguments
        -: 4998:	for (size_t i = 0; i < items; i += 2) {
        -: 4999:	  char*restrict key = SvPV_nolen(ST(i));
        -: 5000:	  SV*restrict val   = ST(i + 1);
        -: 5001:	  if (strEQ(key, "data")) {
        -: 5002:		   data_sv = val;
        -: 5003:	  } else if (strEQ(key, "nrow")) {
        -: 5004:		   nrow = (size_t)SvUV(val);
        -: 5005:		   nrow_set = TRUE;
        -: 5006:	  } else if (strEQ(key, "ncol")) {
        -: 5007:		   ncol = (size_t)SvUV(val);
        -: 5008:		   ncol_set = TRUE;
        -: 5009:	  } else if (strEQ(key, "byrow")) {
        -: 5010:		   byrow = SvTRUE(val);
        -: 5011:	  } else {
        -: 5012:		   croak("Unknown option: %s", key);
        -: 5013:	  }
        -: 5014:	}
        -: 5015:	// Validate data input
        -: 5016:	if (!data_sv || !SvROK(data_sv) || SvTYPE(SvRV(data_sv)) != SVt_PVAV) {
        -: 5017:	  croak("The 'data' option must be an array reference (e.g. data => [1..6])");
        -: 5018:	}
        -: 5019:	AV*restrict data_av = (AV*)SvRV(data_sv);
        -: 5020:	size_t data_len = (UV)(av_top_index(data_av) + 1);
        -: 5021:	if (data_len == 0) {
        -: 5022:	  croak("Data array cannot be empty");
        -: 5023:	}
        -: 5024:	// R-style dimension inference
        -: 5025:	if (!nrow_set && !ncol_set) {
        -: 5026:	  nrow = data_len;
        -: 5027:	  ncol = 1;
        -: 5028:	} else if (nrow_set && !ncol_set) {
        -: 5029:	  ncol = (data_len + nrow - 1) / nrow;
        -: 5030:	} else if (!nrow_set && ncol_set) {
        -: 5031:	  nrow = (data_len + ncol - 1) / ncol;
        -: 5032:	}
        -: 5033:	// Final safety check for dimensions
        -: 5034:	if (nrow == 0 || ncol == 0) {
        -: 5035:	  croak("Dimensions must be greater than 0");
        -: 5036:	}
        -: 5037:	// Create the matrix (Array of Arrays)
        -: 5038:	AV*restrict result_av = newAV();
        -: 5039:	av_extend(result_av, nrow - 1);
        -: 5040:	size_t r, c;// Use unsigned types for counters to prevent negative indexing
        -: 5041:	AV**restrict row_ptrs = (AV**restrict)safemalloc(nrow * sizeof(AV*)); /* Pre-allocate row pointers */
        -: 5042:	for (r = 0; r < nrow; r++) {
        -: 5043:	  row_ptrs[r] = newAV();
        -: 5044:	  av_extend(row_ptrs[r], ncol - 1);
        -: 5045:	  av_push(result_av, newRV_noinc((SV*)row_ptrs[r]));
        -: 5046:	}
        -: 5047:	// Fill the matrix
        -: 5048:	size_t total_cells = nrow * ncol;
        -: 5049:	for (size_t i = 0; i < total_cells; i++) {
        -: 5050:	  // Vector recycling logic
        -: 5051:	  SV**restrict fetched = av_fetch(data_av, i % data_len, 0);
        -: 5052:	  SV*restrict val = fetched ? newSVsv(*fetched) : newSV(0);
        -: 5053:	  if (byrow) {
        -: 5054:		   r = i / ncol;
        -: 5055:		   c = i % ncol;
        -: 5056:	  } else {
        -: 5057:		   r = i % nrow;
        -: 5058:		   c = i / nrow;
        -: 5059:	  }
        -: 5060:	  av_store(row_ptrs[r], c, val);
        -: 5061:	}
        -: 5062:	safefree(row_ptrs);
        -: 5063:	RETVAL = newRV_noinc((SV*)result_av);
        -: 5064:#line 5065 "LikeR.c"
        3: 5065:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 3
        3: 5066:	ST(0) = RETVAL;
        -: 5067:    }
        3: 5068:    XSRETURN(1);
        -: 5069:}
        -: 5070:
        -: 5071:
        -: 5072:XS_EUPXS(XS_Stats__LikeR_lm); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_lm called 66 returned 73% blocks executed 80%
       66: 5073:XS_EUPXS(XS_Stats__LikeR_lm)
        -: 5074:{
       66: 5075:    dVAR; dXSARGS;
       66: 5075-block  0
call    0 returned 66
branch  1 taken 0 (fallthrough)
branch  2 taken 66
        -: 5076:    PERL_UNUSED_VAR(cv); /* -W */
        -: 5077:    PERL_UNUSED_VAR(items); /* -W */
        -: 5078:    {
        -: 5079:	SV *	RETVAL;
        -: 5080:#line 4557 "LikeR.xs"
        -: 5081:{
        -: 5082:	const char *restrict formula  = NULL;
        -: 5083:	SV *restrict data_sv = NULL;
        -: 5084:	char f_cpy[512];
        -: 5085:	char *restrict src, *restrict dst, *restrict tilde, *restrict lhs, *restrict rhs, *restrict chunk;
        -: 5086:
        -: 5087:	char **restrict terms = NULL, **restrict uniq_terms = NULL, **restrict exp_terms = NULL;
        -: 5088:	bool *restrict is_dummy = NULL;
        -: 5089:	char **restrict dummy_base = NULL, **restrict dummy_level = NULL;
        -: 5090:	unsigned int term_cap = 64, exp_cap = 64, num_terms = 0, num_uniq = 0, p = 0, p_exp = 0;
        -: 5091:	size_t n = 0, valid_n = 0, i, j, k, l, l1, l2;
        -: 5092:	bool has_intercept = true;
        -: 5093:
        -: 5094:	char **restrict row_names = NULL, **restrict valid_row_names = NULL;
        -: 5095:	HV **restrict row_hashes = NULL;
        -: 5096:	HV *restrict data_hoa = NULL;
        -: 5097:	SV *restrict ref = NULL;
        -: 5098:
        -: 5099:	double *restrict X = NULL, *restrict Y = NULL, *restrict XtX = NULL, *restrict XtY = NULL;
        -: 5100:	bool *restrict aliased = NULL;
        -: 5101:	double *restrict beta = NULL;
        -: 5102:	int final_rank = 0, df_res = 0;
        -: 5103:	HV *restrict res_hv, *restrict coef_hv, *restrict fitted_hv, *restrict resid_hv, *restrict summary_hv;
        -: 5104:	AV *restrict terms_av;
        -: 5105:	double rss = 0.0, rse_sq = 0.0;
        -: 5106:	HE *restrict entry;
        -: 5107:
        -: 5108:	if (items % 2 != 0) croak("Usage: lm(formula => 'mpg ~ wt * hp', data => \\%%mtcars)");
        -: 5109:
        -: 5110:	for (unsigned short i_arg = 0; i_arg < items; i_arg += 2) {
        -: 5111:	  const char *restrict key = SvPV_nolen(ST(i_arg));
        -: 5112:	  SV *restrict val = ST(i_arg + 1);
        -: 5113:	  if      (strEQ(key, "formula")) formula = SvPV_nolen(val);
        -: 5114:	  else if (strEQ(key, "data"))    data_sv = val;
        -: 5115:	  else croak("lm: unknown argument '%s'", key);
        -: 5116:	}
        -: 5117:	if (!formula) croak("lm: formula is required");
        -: 5118:	if (!data_sv || !SvROK(data_sv)) croak("lm: data is required and must be a reference");
        -: 5119:
        -: 5120:	// ========================================================================
        -: 5121:	// PHASE 1: Data Extraction
        -: 5122:	// ========================================================================
        -: 5123:	ref = SvRV(data_sv);
        -: 5124:	if (SvTYPE(ref) == SVt_PVHV) {
        -: 5125:	  HV *restrict hv = (HV*)ref;
        -: 5126:	  if (hv_iterinit(hv) == 0) croak("lm: Data hash is empty");
        -: 5127:	  entry = hv_iternext(hv);
        -: 5128:	  if (entry) {
        -: 5129:		   SV *restrict val = hv_iterval(hv, entry);
        -: 5130:		   if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
        -: 5131:		       data_hoa = hv;
        -: 5132:		       n = av_len((AV*)SvRV(val)) + 1;
        -: 5133:		       Newx(row_names, n, char*);
        -: 5134:		       for (i = 0; i < n; i++) {
        -: 5135:		           char buf[32];
        -: 5136:		           snprintf(buf, sizeof(buf), "%lu", (unsigned long)(i + 1));
        -: 5137:		           row_names[i] = savepv(buf);
        -: 5138:		       }
        -: 5139:		   } else if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVHV) {
        -: 5140:		       n = hv_iterinit(hv);
        -: 5141:		       Newx(row_names, n, char*); Newx(row_hashes, n, HV*);
        -: 5142:		       i = 0;
        -: 5143:		       while ((entry = hv_iternext(hv))) {
        -: 5144:		           unsigned int len;
        -: 5145:		           row_names[i] = savepv(hv_iterkey(entry, &len));
        -: 5146:		           row_hashes[i] = (HV*)SvRV(hv_iterval(hv, entry));
        -: 5147:		           i++;
        -: 5148:		       }
        -: 5149:		   } else croak("lm: Hash values must be ArrayRefs (HoA) or HashRefs (HoH)");
        -: 5150:	  }
        -: 5151:	} else if (SvTYPE(ref) == SVt_PVAV) {
        -: 5152:	  AV *restrict av = (AV*)ref; n = av_len(av) + 1;
        -: 5153:	  Newx(row_names, n, char*);
        -: 5154:	  Newx(row_hashes, n, HV*);
        -: 5155:	  for (i = 0; i < n; i++) {
        -: 5156:		   SV **restrict val = av_fetch(av, i, 0);
        -: 5157:		   if (val && SvROK(*val) && SvTYPE(SvRV(*val)) == SVt_PVHV) {
        -: 5158:		       row_hashes[i] = (HV*)SvRV(*val);
        -: 5159:		       char buf[32]; snprintf(buf, sizeof(buf), "%lu", (unsigned long)(i + 1));
        -: 5160:		       row_names[i] = savepv(buf);
        -: 5161:		   } else {
        -: 5162:		       for (k = 0; k < i; k++) Safefree(row_names[k]);
        -: 5163:		       Safefree(row_names); Safefree(row_hashes);
        -: 5164:		       croak("lm: Array values must be HashRefs (AoH)");
        -: 5165:		   }
        -: 5166:	  }
        -: 5167:	} else croak("lm: Data must be an Array or Hash reference");
        -: 5168:
        -: 5169:	// ========================================================================
        -: 5170:	// PHASE 2: Formula Parsing & `.` Expansion
        -: 5171:	// ========================================================================
        -: 5172:	src = (char*)formula; dst = f_cpy;
        -: 5173:	while (*src && (dst - f_cpy < 511)) { if (!isspace(*src)) { *dst++ = *src; } src++; }
        -: 5174:	*dst = '\0';
        -: 5175:
        -: 5176:	tilde = strchr(f_cpy, '~');
        -: 5177:	if (!tilde) {
        -: 5178:	  for (i = 0; i < n; i++) Safefree(row_names[i]);
        -: 5179:	  Safefree(row_names); if (row_hashes) Safefree(row_hashes);
        -: 5180:	  croak("lm: invalid formula, missing '~'");
        -: 5181:	}
        -: 5182:	*tilde = '\0';
        -: 5183:	lhs = f_cpy;
        -: 5184:	rhs = tilde + 1;
        -: 5185:
        -: 5186:	// Remove intercept-suppression markers from RHS.
        -: 5187:	// IMPORTANT: skip tokens that appear inside I(...) wrappers so that
        -: 5188:	// expressions like I(x^-1) are never mistakenly treated as "-1".
        -: 5189:	{
        -: 5190:	  char *restrict p_idx = rhs;
        -: 5191:	  while (*p_idx) {
        -: 5192:		   // Skip over I(...) sub-expressions entirely
        -: 5193:		   if (p_idx[0] == 'I' && p_idx[1] == '(') {
        -: 5194:		       int depth = 0;
        -: 5195:		       while (*p_idx) { if (*p_idx == '(') depth++; else if (*p_idx == ')') { depth--; if (depth == 0) { p_idx++; break; } } p_idx++; }
        -: 5196:		       continue;
        -: 5197:		   }
        -: 5198:		   // Match bare -1
        -: 5199:		   if (p_idx[0] == '-' && p_idx[1] == '1' &&
        -: 5200:		       (p_idx[2] == '\0' || p_idx[2] == '+' || p_idx[2] == '-')) {
        -: 5201:		       has_intercept = false;
        -: 5202:		       memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1);
        -: 5203:		       continue; // re-examine same position
        -: 5204:		   }
        -: 5205:		   // Match +0
        -: 5206:		   if (p_idx[0] == '+' && p_idx[1] == '0' &&
        -: 5207:		       (p_idx[2] == '\0' || p_idx[2] == '+' || p_idx[2] == '-')) {
        -: 5208:		       has_intercept = false;
        -: 5209:		       memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1);
        -: 5210:		       continue;
        -: 5211:		   }
        -: 5212:		   // Match leading 0+
        -: 5213:		   if (p_idx == rhs && p_idx[0] == '0' && p_idx[1] == '+') {
        -: 5214:		       has_intercept = false;
        -: 5215:		       memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1);
        -: 5216:		       continue;
        -: 5217:		   }
        -: 5218:		   // Match bare 0 (entire rhs)
        -: 5219:		   if (p_idx == rhs && p_idx[0] == '0' && p_idx[1] == '\0') {
        -: 5220:		       has_intercept = false; p_idx[0] = '\0'; break;
        -: 5221:		   }
        -: 5222:		   // Strip redundant +1 (keep intercept, just remove marker)
        -: 5223:		   if (p_idx[0] == '+' && p_idx[1] == '1' &&
        -: 5224:		       (p_idx[2] == '\0' || p_idx[2] == '+' || p_idx[2] == '-')) {
        -: 5225:		       memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1);
        -: 5226:		       continue;
        -: 5227:		   }
        -: 5228:		   // Strip leading bare 1 or 1+
        -: 5229:		   if (p_idx == rhs) {
        -: 5230:		       if (p_idx[0] == '1' && p_idx[1] == '\0') { p_idx[0] = '\0'; break; }
        -: 5231:		       if (p_idx[0] == '1' && p_idx[1] == '+') { memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1); continue; }
        -: 5232:		   }
        -: 5233:		   p_idx++;
        -: 5234:	  }
        -: 5235:	}
        -: 5236:
        -: 5237:	// Clean up stray `++`, leading `+`, trailing `+`
        -: 5238:	{
        -: 5239:	  char *restrict p_idx;
        -: 5240:	  while ((p_idx = strstr(rhs, "++")) != NULL)
        -: 5241:		   memmove(p_idx, p_idx + 1, strlen(p_idx + 1) + 1);
        -: 5242:	  if (rhs[0] == '+') memmove(rhs, rhs + 1, strlen(rhs + 1) + 1);
        -: 5243:	  size_t len_rhs = strlen(rhs);
        -: 5244:	  if (len_rhs > 0 && rhs[len_rhs - 1] == '+') rhs[len_rhs - 1] = '\0';
        -: 5245:	}
        -: 5246:
        -: 5247:	// Expand `.` Operator
        -: 5248:	char rhs_expanded[2048] = "";
        -: 5249:	size_t rhs_len = 0;
        -: 5250:	chunk = strtok(rhs, "+");
        -: 5251:	while (chunk != NULL) {
        -: 5252:	  if (strcmp(chunk, ".") == 0) {
        -: 5253:		   AV *cols = get_all_columns(data_hoa, row_hashes, n);
        -: 5254:		   for (size_t c = 0; c <= (size_t)av_len(cols); c++) {
        -: 5255:		       SV **col_sv = av_fetch(cols, c, 0);
        -: 5256:		       if (col_sv && SvOK(*col_sv)) {
        -: 5257:		           const char *col_name = SvPV_nolen(*col_sv);
        -: 5258:		           if (strcmp(col_name, lhs) != 0) {
        -: 5259:		               size_t slen = strlen(col_name);
        -: 5260:		               if (rhs_len + slen + 2 < sizeof(rhs_expanded)) {
        -: 5261:		                   if (rhs_len > 0) { strcat(rhs_expanded, "+"); rhs_len++; }
        -: 5262:		                   strcat(rhs_expanded, col_name);
        -: 5263:		                   rhs_len += slen;
        -: 5264:		               }
        -: 5265:		           }
        -: 5266:		       }
        -: 5267:		   }
        -: 5268:		   SvREFCNT_dec(cols);
        -: 5269:	  } else {
        -: 5270:		   size_t slen = strlen(chunk);
        -: 5271:		   if (rhs_len + slen + 2 < sizeof(rhs_expanded)) {
        -: 5272:		       if (rhs_len > 0) { strcat(rhs_expanded, "+"); rhs_len++; }
        -: 5273:		       strcat(rhs_expanded, chunk);
        -: 5274:		       rhs_len += slen;
        -: 5275:		   }
        -: 5276:	  }
        -: 5277:	  chunk = strtok(NULL, "+");
        -: 5278:	}
        -: 5279:
        -: 5280:	Newx(terms, term_cap, char*); Newx(uniq_terms, term_cap, char*);
        -: 5281:	Newx(exp_terms, exp_cap, char*); Newx(is_dummy, exp_cap, bool);
        -: 5282:	Newx(dummy_base, exp_cap, char*); Newx(dummy_level, exp_cap, char*);
        -: 5283:
        -: 5284:	if (has_intercept) { terms[num_terms++] = savepv("Intercept"); }
        -: 5285:
        -: 5286:	if (strlen(rhs_expanded) > 0) {
        -: 5287:	  chunk = strtok(rhs_expanded, "+");
        -: 5288:	  while (chunk != NULL) {
        -: 5289:		   if (num_terms >= term_cap - 3) {
        -: 5290:		       term_cap *= 2;
        -: 5291:		       Renew(terms, term_cap, char*); Renew(uniq_terms, term_cap, char*);
        -: 5292:		   }
        -: 5293:		   char *restrict star = strchr(chunk, '*');
        -: 5294:		   if (star) {
        -: 5295:		       *star = '\0';
        -: 5296:		       char *restrict left = chunk;
        -: 5297:		       char *restrict right = star + 1;
        -: 5298:		       char *restrict c_l = strchr(left, '^');
        -: 5299:		       if (c_l && strncmp(left, "I(", 2) != 0) *c_l = '\0';
        -: 5300:		       char *restrict c_r = strchr(right, '^');
        -: 5301:		       if (c_r && strncmp(right, "I(", 2) != 0) *c_r = '\0';
        -: 5302:		       terms[num_terms++] = savepv(left);
        -: 5303:		       terms[num_terms++] = savepv(right);
        -: 5304:		       size_t inter_len = strlen(left) + strlen(right) + 2;
        -: 5305:		       terms[num_terms] = (char*)safemalloc(inter_len);
        -: 5306:		       snprintf(terms[num_terms++], inter_len, "%s:%s", left, right);
        -: 5307:		   } else {
        -: 5308:		       char *restrict c_chunk = strchr(chunk, '^');
        -: 5309:		       if (c_chunk && strncmp(chunk, "I(", 2) != 0) *c_chunk = '\0';
        -: 5310:		       terms[num_terms++] = savepv(chunk);
        -: 5311:		   }
        -: 5312:		   chunk = strtok(NULL, "+");
        -: 5313:	  }
        -: 5314:	}
        -: 5315:
        -: 5316:	for (i = 0; i < num_terms; i++) {
        -: 5317:	  bool found = false;
        -: 5318:	  for (j = 0; j < num_uniq; j++) { if (strcmp(terms[i], uniq_terms[j]) == 0) { found = true; break; } }
        -: 5319:	  if (!found) uniq_terms[num_uniq++] = savepv(terms[i]);
        -: 5320:	}
        -: 5321:	p = num_uniq;
        -: 5322:
        -: 5323:	// ========================================================================
        -: 5324:	// PHASE 3: Categorical Expansion
        -: 5325:	// ========================================================================
        -: 5326:	for (j = 0; j < p; j++) {
        -: 5327:	  if (p_exp + 32 >= exp_cap) {
        -: 5328:		   exp_cap *= 2;
        -: 5329:		   Renew(exp_terms, exp_cap, char*); Renew(is_dummy, exp_cap, bool);
        -: 5330:		   Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 5331:	  }
        -: 5332:	  if (strcmp(uniq_terms[j], "Intercept") == 0) {
        -: 5333:		   exp_terms[p_exp] = savepv("Intercept"); is_dummy[p_exp] = false; p_exp++; continue;
        -: 5334:	  }
        -: 5335:	  if (is_column_categorical(data_hoa, row_hashes, n, uniq_terms[j])) {
        -: 5336:		   char **restrict levels = NULL;
        -: 5337:		   unsigned int num_levels = 0, levels_cap = 8;
        -: 5338:		   Newx(levels, levels_cap, char*);
        -: 5339:		   for (i = 0; i < n; i++) {
        -: 5340:		       char *str_val = get_data_string_alloc(data_hoa, row_hashes, i, uniq_terms[j]);
        -: 5341:		       if (str_val) {
        -: 5342:		           bool found = false;
        -: 5343:		           for (l = 0; l < num_levels; l++) { if (strcmp(levels[l], str_val) == 0) { found = true; break; } }
        -: 5344:		           if (!found) {
        -: 5345:		               if (num_levels >= levels_cap) { levels_cap *= 2; Renew(levels, levels_cap, char*); }
        -: 5346:		               levels[num_levels++] = savepv(str_val);
        -: 5347:		           }
        -: 5348:		           Safefree(str_val);
        -: 5349:		       }
        -: 5350:		   }
        -: 5351:		   if (num_levels > 0) {
        -: 5352:		       for (l1 = 0; l1 < num_levels - 1; l1++)
        -: 5353:		           for (l2 = l1 + 1; l2 < num_levels; l2++)
        -: 5354:		               if (strcmp(levels[l1], levels[l2]) > 0) { char *tmp = levels[l1]; levels[l1] = levels[l2]; levels[l2] = tmp; }
        -: 5355:		       for (l = 1; l < num_levels; l++) {
        -: 5356:		           if (p_exp >= exp_cap) {
        -: 5357:		               exp_cap *= 2;
        -: 5358:		               Renew(exp_terms, exp_cap, char*); Renew(is_dummy, exp_cap, bool);
        -: 5359:		               Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 5360:		           }
        -: 5361:		           size_t t_len = strlen(uniq_terms[j]) + strlen(levels[l]) + 1;
        -: 5362:		           exp_terms[p_exp] = (char*)safemalloc(t_len);
        -: 5363:		           snprintf(exp_terms[p_exp], t_len, "%s%s", uniq_terms[j], levels[l]);
        -: 5364:		           is_dummy[p_exp] = true;
        -: 5365:		           dummy_base[p_exp]  = savepv(uniq_terms[j]);
        -: 5366:		           dummy_level[p_exp] = savepv(levels[l]);
        -: 5367:		           p_exp++;
        -: 5368:		       }
        -: 5369:		       for (l = 0; l < num_levels; l++) Safefree(levels[l]);
        -: 5370:		       Safefree(levels);
        -: 5371:		   } else {
        -: 5372:		       Safefree(levels);
        -: 5373:		       exp_terms[p_exp] = savepv(uniq_terms[j]); is_dummy[p_exp] = false; p_exp++;
        -: 5374:		   }
        -: 5375:	  } else {
        -: 5376:		   exp_terms[p_exp] = savepv(uniq_terms[j]); is_dummy[p_exp] = false; p_exp++;
        -: 5377:	  }
        -: 5378:	}
        -: 5379:	p = p_exp;
        -: 5380:	Newx(X, n * p, double); Newx(Y, n, double);
        -: 5381:	Newx(valid_row_names, n, char*);
        -: 5382:
        -: 5383:	// ========================================================================
        -: 5384:	// PHASE 4: Matrix Construction & Listwise Deletion
        -: 5385:	// ========================================================================
        -: 5386:	for (i = 0; i < n; i++) {
        -: 5387:	  double y_val = evaluate_term(data_hoa, row_hashes, i, lhs);
        -: 5388:	  if (isnan(y_val)) { Safefree(row_names[i]); continue; }
        -: 5389:
        -: 5390:	  bool row_ok = true;
        -: 5391:	  double *restrict row_x = (double*)safemalloc(p * sizeof(double));
        -: 5392:	  for (j = 0; j < p; j++) {
        -: 5393:		   if (strcmp(exp_terms[j], "Intercept") == 0) {
        -: 5394:		       row_x[j] = 1.0;
        -: 5395:		   } else if (is_dummy[j]) {
        -: 5396:		       char *restrict str_val = get_data_string_alloc(data_hoa, row_hashes, i, dummy_base[j]);
        -: 5397:		       if (str_val) {
        -: 5398:		           row_x[j] = (strcmp(str_val, dummy_level[j]) == 0) ? 1.0 : 0.0;
        -: 5399:		           Safefree(str_val);
        -: 5400:		       } else { row_ok = false; break; }
        -: 5401:		   } else {
        -: 5402:		       row_x[j] = evaluate_term(data_hoa, row_hashes, i, exp_terms[j]);
        -: 5403:		       if (isnan(row_x[j])) { row_ok = false; break; }
        -: 5404:		   }
        -: 5405:	  }
        -: 5406:	  if (!row_ok) { Safefree(row_names[i]); Safefree(row_x); continue; }
        -: 5407:
        -: 5408:	  Y[valid_n] = y_val;
        -: 5409:	  for (j = 0; j < p; j++) X[valid_n * p + j] = row_x[j];
        -: 5410:	  valid_row_names[valid_n] = row_names[i];
        -: 5411:	  valid_n++;
        -: 5412:	  Safefree(row_x);
        -: 5413:	}
        -: 5414:	Safefree(row_names);
        -: 5415:
        -: 5416:	if (valid_n <= p) {
        -: 5417:	  for (i = 0; i < num_terms; i++) Safefree(terms[i]); Safefree(terms);
        -: 5418:	  for (i = 0; i < num_uniq; i++) Safefree(uniq_terms[i]); Safefree(uniq_terms);
        -: 5419:	  for (j = 0; j < p_exp; j++) {
        -: 5420:		   Safefree(exp_terms[j]);
        -: 5421:		   if (is_dummy[j]) { Safefree(dummy_base[j]); Safefree(dummy_level[j]); }
        -: 5422:	  }
        -: 5423:	  Safefree(exp_terms); Safefree(is_dummy); Safefree(dummy_base); Safefree(dummy_level);
        -: 5424:	  Safefree(X); Safefree(Y); Safefree(valid_row_names);
        -: 5425:	  if (row_hashes) Safefree(row_hashes);
        -: 5426:	  croak("lm: 0 degrees of freedom (too many NAs or parameters > observations)");
        -: 5427:	}
        -: 5428:
        -: 5429:	// ========================================================================
        -: 5430:	// PHASE 5: OLS Math
        -: 5431:	// ========================================================================
        -: 5432:	Newxz(XtX, p * p, double);
        -: 5433:	for (i = 0; i < p; i++)
        -: 5434:	  for (j = 0; j < p; j++) {
        -: 5435:		   double sum = 0.0;
        -: 5436:		   for (k = 0; k < valid_n; k++) sum += X[k * p + i] * X[k * p + j];
        -: 5437:		   XtX[i * p + j] = sum;
        -: 5438:	  }
        -: 5439:	Newxz(XtY, p, double);
        -: 5440:	for (i = 0; i < p; i++) {
        -: 5441:	  double sum = 0.0;
        -: 5442:	  for (k = 0; k < valid_n; k++) sum += X[k * p + i] * Y[k];
        -: 5443:	  XtY[i] = sum;
        -: 5444:	}
        -: 5445:	Newx(aliased, p, bool);
        -: 5446:	final_rank = sweep_matrix_ols(XtX, p, aliased);
        -: 5447:	Newxz(beta, p, double);
        -: 5448:	for (i = 0; i < p; i++) {
        -: 5449:	  if (aliased[i]) { beta[i] = NAN; }
        -: 5450:	  else {
        -: 5451:		   double sum = 0.0;
        -: 5452:		   for (j = 0; j < p; j++) if (!aliased[j]) sum += XtX[i * p + j] * XtY[j];
        -: 5453:		   beta[i] = sum;
        -: 5454:	  }
        -: 5455:	}
        -: 5456:
        -: 5457:	// ========================================================================
        -: 5458:	// PHASE 6: Metrics & Cleanup
        -: 5459:	// ========================================================================
        -: 5460:	res_hv = newHV(); coef_hv = newHV(); fitted_hv = newHV(); resid_hv = newHV();
        -: 5461:	summary_hv = newHV(); terms_av = newAV();
        -: 5462:
        -: 5463:	df_res = (int)valid_n - final_rank;
        -: 5464:
        -: 5465:	// rss / mss accumulated here — rse_sq computed AFTER this loop (not before)
        -: 5466:	double sum_y = 0.0, mss = 0.0;
        -: 5467:	for (i = 0; i < valid_n; i++) sum_y += Y[i];
        -: 5468:	double mean_y = sum_y / (double)valid_n;
        -: 5469:
        -: 5470:	for (i = 0; i < valid_n; i++) {
        -: 5471:	  double y_hat = 0.0;
        -: 5472:	  for (j = 0; j < p; j++) if (!aliased[j]) y_hat += X[i * p + j] * beta[j];
        -: 5473:	  double res   = Y[i] - y_hat;
        -: 5474:	  rss          += res * res;
        -: 5475:	  double diff_m = has_intercept ? (y_hat - mean_y) : y_hat;
        -: 5476:	  mss          += diff_m * diff_m;
        -: 5477:	  hv_store(fitted_hv, valid_row_names[i], strlen(valid_row_names[i]), newSVnv(y_hat), 0);
        -: 5478:	  hv_store(resid_hv,  valid_row_names[i], strlen(valid_row_names[i]), newSVnv(res),   0);
        -: 5479:	  Safefree(valid_row_names[i]);
        -: 5480:	}
        -: 5481:	Safefree(valid_row_names);
        -: 5482:
        -: 5483:	// Single, authoritative rse_sq calculation
        -: 5484:	rse_sq = (df_res > 0) ? (rss / (double)df_res) : NAN;
        -: 5485:
        -: 5486:	int df_int = has_intercept ? 1 : 0;
        -: 5487:	double r_squared = 0.0, adj_r_squared = 0.0, f_stat = NAN, f_pvalue = NAN;
        -: 5488:	int numdf = final_rank - df_int;
        -: 5489:
        -: 5490:	if (final_rank != df_int && (mss + rss) > 0.0) {
        -: 5491:	  r_squared     = mss / (mss + rss);
        -: 5492:	  adj_r_squared = 1.0 - (1.0 - r_squared) * ((valid_n - df_int) / (double)df_res);
        -: 5493:	  if (rse_sq > 0.0 && numdf > 0) {
        -: 5494:		   f_stat   = (mss / (double)numdf) / rse_sq;
        -: 5495:		   f_pvalue = 1.0 - pf(f_stat, (double)numdf, (double)df_res);
        -: 5496:	  } else if (rse_sq == 0.0) {
        -: 5497:		   f_stat   = INFINITY;
        -: 5498:		   f_pvalue = 0.0;
        -: 5499:	  }
        -: 5500:	} else if (final_rank == df_int) {
        -: 5501:	  r_squared = 0.0; adj_r_squared = 0.0;
        -: 5502:	}
        -: 5503:
        -: 5504:	for (j = 0; j < p; j++) {
        -: 5505:	  hv_store(coef_hv, exp_terms[j], strlen(exp_terms[j]), newSVnv(beta[j]), 0);
        -: 5506:	  av_push(terms_av, newSVpv(exp_terms[j], 0));
        -: 5507:	  HV *restrict row_hv = newHV();
        -: 5508:	  if (aliased[j]) {
        -: 5509:		   hv_store(row_hv, "Estimate",   8,  newSVpv("NaN", 0), 0);
        -: 5510:		   hv_store(row_hv, "Std. Error", 10, newSVpv("NaN", 0), 0);
        -: 5511:		   hv_store(row_hv, "t value",    7,  newSVpv("NaN", 0), 0);
        -: 5512:		   hv_store(row_hv, "Pr(>|t|)",   8,  newSVpv("NaN", 0), 0);
        -: 5513:	  } else {
        -: 5514:		   double se    = sqrt(rse_sq * XtX[j * p + j]);
        -: 5515:		   double t_val = (se > 0.0) ? (beta[j] / se) : (INFINITY * (beta[j] >= 0.0 ? 1.0 : -1.0));
        -: 5516:		   double p_val = get_t_pvalue(t_val, df_res, "two.sided");
        -: 5517:		   hv_store(row_hv, "Estimate",   8,  newSVnv(beta[j]), 0);
        -: 5518:		   hv_store(row_hv, "Std. Error", 10, newSVnv(se),      0);
        -: 5519:		   hv_store(row_hv, "t value",    7,  newSVnv(t_val),   0);
        -: 5520:		   hv_store(row_hv, "Pr(>|t|)",   8,  newSVnv(p_val),   0);
        -: 5521:	  }
        -: 5522:	  hv_store(summary_hv, exp_terms[j], strlen(exp_terms[j]), newRV_noinc((SV*)row_hv), 0);
        -: 5523:	}
        -: 5524:
        -: 5525:	hv_store(res_hv, "coefficients",  12, newRV_noinc((SV*)coef_hv),   0);
        -: 5526:	hv_store(res_hv, "fitted.values", 13, newRV_noinc((SV*)fitted_hv), 0);
        -: 5527:	hv_store(res_hv, "residuals",      9, newRV_noinc((SV*)resid_hv),  0);
        -: 5528:	hv_store(res_hv, "df.residual",   11, newSVuv(df_res),             0);
        -: 5529:	hv_store(res_hv, "rank",           4, newSVuv(final_rank),         0);
        -: 5530:	hv_store(res_hv, "rss",            3, newSVnv(rss),                0);
        -: 5531:	hv_store(res_hv, "summary",        7, newRV_noinc((SV*)summary_hv),0);
        -: 5532:	hv_store(res_hv, "terms",          5, newRV_noinc((SV*)terms_av),  0);
        -: 5533:	hv_store(res_hv, "r.squared",      9, newSVnv(r_squared),          0);
        -: 5534:	hv_store(res_hv, "adj.r.squared", 13, newSVnv(adj_r_squared),      0);
        -: 5535:	if (!isnan(f_stat)) {
        -: 5536:	  AV *fstat_av = newAV();
        -: 5537:	  av_push(fstat_av, newSVnv(f_stat));
        -: 5538:	  av_push(fstat_av, newSViv(numdf));
        -: 5539:	  av_push(fstat_av, newSViv(df_res));
        -: 5540:	  hv_store(res_hv, "fstatistic", 10, newRV_noinc((SV*)fstat_av), 0);
        -: 5541:	  hv_store(res_hv, "f.pvalue",    8, newSVnv(f_pvalue),          0);
        -: 5542:	}
        -: 5543:
        -: 5544:	// Deep Cleanup
        -: 5545:	for (i = 0; i < num_terms; i++) Safefree(terms[i]); Safefree(terms);
        -: 5546:	for (i = 0; i < num_uniq; i++) Safefree(uniq_terms[i]); Safefree(uniq_terms);
        -: 5547:	for (j = 0; j < p_exp; j++) {
        -: 5548:	  Safefree(exp_terms[j]);
        -: 5549:	  if (is_dummy[j]) { Safefree(dummy_base[j]); Safefree(dummy_level[j]); }
        -: 5550:	}
        -: 5551:	Safefree(exp_terms); Safefree(is_dummy); Safefree(dummy_base); Safefree(dummy_level);
        -: 5552:	Safefree(X); Safefree(Y); Safefree(XtX); Safefree(XtY);
        -: 5553:	Safefree(beta); Safefree(aliased);
        -: 5554:	if (row_hashes) Safefree(row_hashes);
        -: 5555:
        -: 5556:	RETVAL = newRV_noinc((SV*)res_hv);
        -: 5557:}
        -: 5558:#line 5559 "LikeR.c"
       48: 5559:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 48
       48: 5560:	ST(0) = RETVAL;
        -: 5561:    }
       48: 5562:    XSRETURN(1);
        -: 5563:}
        -: 5564:
        -: 5565:
        -: 5566:XS_EUPXS(XS_Stats__LikeR_seq); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_seq called 18 returned 100% blocks executed 67%
       18: 5567:XS_EUPXS(XS_Stats__LikeR_seq)
        -: 5568:{
       18: 5569:    dVAR; dXSARGS;
       18: 5569-block  0
call    0 returned 18
       18: 5570:    if (items < 2 || items > 3)
branch  0 taken 18 (fallthrough)
branch  1 taken 0
       18: 5570-block  0
branch  2 taken 0 (fallthrough)
branch  3 taken 18
    #####: 5571:       croak_xs_usage(cv,  "from, to, by = 1.0");
    %%%%%: 5571-block  0
call    0 never executed
        -: 5572:    PERL_UNUSED_VAR(ax); /* -Wall */
       18: 5573:    SP -= items;
        -: 5574:    {
       18: 5575:	double	from = (double)SvNV(ST(0))
       18: 5575-block  0
call    0 returned 18
        -: 5576:;
       18: 5577:	double	to = (double)SvNV(ST(1))
call    0 returned 18
        -: 5578:;
        -: 5579:	double	by;
        -: 5580:
       18: 5581:	if (items < 3)
branch  0 taken 3 (fallthrough)
branch  1 taken 15
        3: 5582:	    by = 1.0;
        3: 5582-block  0
        -: 5583:	else {
       15: 5584:	    by = (double)SvNV(ST(2))
       15: 5584-block  0
call    0 returned 15
        -: 5585:;
        -: 5586:	}
        -: 5587:#line 5042 "LikeR.xs"
        -: 5588:	{
        -: 5589:		//Handle the zero 'by' case
        -: 5590:		if (by == 0.0) {
        -: 5591:			if (from == to) {
        -: 5592:				 EXTEND(SP, 1);
        -: 5593:				 mPUSHn(from);
        -: 5594:				 XSRETURN(1);
        -: 5595:			} else {
        -: 5596:				 croak("invalid 'by' argument: cannot be zero when from != to");
        -: 5597:			}
        -: 5598:		}
        -: 5599:		// Check for wrong direction / infinite loop
        -: 5600:		if ((from < to && by < 0.0) || (from > to && by > 0.0)) {
        -: 5601:			croak("wrong sign in 'by' argument");
        -: 5602:		}
        -: 5603:		/* * Calculate number of elements. 
        -: 5604:		* R uses a small epsilon (like 1e-10) to avoid dropping the last 
        -: 5605:		* element due to floating point inaccuracies.
        -: 5606:		*/
        -: 5607:		double n_elements_d = (to - from) / by;
        -: 5608:		if (n_elements_d < 0.0) n_elements_d = 0.0;
        -: 5609:		size_t n_elements = (n_elements_d + 1e-10) + 1;
        -: 5610:		// Pre-extend the stack to avoid reallocating inside the loop
        -: 5611:		EXTEND(SP, n_elements);
        -: 5612:		for (size_t i = 0; i < n_elements; i++) {
        -: 5613:			mPUSHn(from + i * by);
        -: 5614:		}
        -: 5615:		XSRETURN(n_elements);
        -: 5616:	}
        -: 5617:#line 5618 "LikeR.c"
        -: 5618:	PUTBACK;
        -: 5619:	return;
        -: 5620:    }
        -: 5621:}
        -: 5622:
        -: 5623:
        -: 5624:XS_EUPXS(XS_Stats__LikeR_rnorm); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_rnorm called 6 returned 50% blocks executed 87%
        6: 5625:XS_EUPXS(XS_Stats__LikeR_rnorm)
        -: 5626:{
        6: 5627:    dVAR; dXSARGS;
        6: 5627-block  0
call    0 returned 6
branch  1 taken 3 (fallthrough)
branch  2 taken 3
        -: 5628:    PERL_UNUSED_VAR(cv); /* -W */
        -: 5629:    PERL_UNUSED_VAR(items); /* -W */
        -: 5630:    {
        -: 5631:	SV *	RETVAL;
        -: 5632:#line 5074 "LikeR.xs"
        -: 5633:    {
        -: 5634:        // Auto-seed the PRNG if the Perl script hasn't done so yet
        -: 5635:        AUTO_SEED_PRNG();
        -: 5636:
        -: 5637:        size_t n = 0;
        -: 5638:        double mean = 0.0, sd = 1.0;
        -: 5639:        int arg_start = 0;
        -: 5640:
        -: 5641:        // Check if the first argument is a simple integer (rnorm(33))
        -: 5642:        if (items > 0 && SvIOK(ST(0)) && (items == 1 || items % 2 != 0)) {
        -: 5643:            n = (unsigned int)SvUV(ST(0));
        -: 5644:            arg_start = 1; // Start parsing named arguments from the second element
        -: 5645:        }
        -: 5646:
        -: 5647:        // --- Parse remaining named arguments from the flat stack ---
        -: 5648:        if ((items - arg_start) % 2 != 0) {
        -: 5649:            croak("Usage: rnorm(n), rnorm(n => 10, mean => 0, sd => 1), or rnorm(33, mean => 0)");
        -: 5650:        }
        -: 5651:
        -: 5652:        for (int i = arg_start; i < items; i += 2) {
        -: 5653:            const char* restrict key = SvPV_nolen(ST(i));
        -: 5654:            SV* restrict val = ST(i + 1);
        -: 5655:
        -: 5656:            if      (strEQ(key, "n"))    n    = (unsigned int)SvUV(val);
        -: 5657:            else if (strEQ(key, "mean")) mean = SvNV(val);
        -: 5658:            else if (strEQ(key, "sd"))   sd   = SvNV(val);
        -: 5659:            else croak("rnorm: unknown argument '%s'", key);
        -: 5660:        }
        -: 5661:
        -: 5662:        if (sd < 0.0) croak("rnorm: standard deviation must be non-negative");
        -: 5663:
        -: 5664:        AV *restrict result_av = newAV();
        -: 5665:        if (n > 0) {
        -: 5666:            av_extend(result_av, n - 1);
        -: 5667:            // Generate random normals using the Box-Muller transform
        -: 5668:            for (size_t i = 0; i < n; ) {
        -: 5669:                 double u, v, s;
        -: 5670:                 do {
        -: 5671:                     // Drand01() hooks into Perl's internal PRNG, respecting Perl's srand()
        -: 5672:                     u = 2.0 * Drand01() - 1.0;
        -: 5673:                     v = 2.0 * Drand01() - 1.0;
        -: 5674:                     s = u * u + v * v;
        -: 5675:                 } while (s >= 1.0 || s == 0.0);
        -: 5676:
        -: 5677:                 double mul = sqrt(-2.0 * log(s) / s);
        -: 5678:                 // Box-Muller generates two independent values per iteration
        -: 5679:                 av_store(result_av, i++, newSVnv(mean + sd * u * mul));
        -: 5680:                 if (i < n) {
        -: 5681:                     av_store(result_av, i++, newSVnv(mean + sd * v * mul));
        -: 5682:                 }
        -: 5683:            }
        -: 5684:        }
        -: 5685:        RETVAL = newRV_noinc((SV*)result_av);
        -: 5686:    }
        -: 5687:#line 5688 "LikeR.c"
        3: 5688:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 3
        3: 5689:	ST(0) = RETVAL;
        -: 5690:    }
        3: 5691:    XSRETURN(1);
        -: 5692:}
        -: 5693:
        -: 5694:
        -: 5695:XS_EUPXS(XS_Stats__LikeR_aov); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_aov called 27 returned 67% blocks executed 78%
       27: 5696:XS_EUPXS(XS_Stats__LikeR_aov)
        -: 5697:{
       27: 5698:    dVAR; dXSARGS;
       27: 5698-block  0
call    0 returned 27
       27: 5699:    if (items != 2)
branch  0 taken 0 (fallthrough)
branch  1 taken 27
    #####: 5700:       croak_xs_usage(cv,  "data_sv, formula_sv");
    %%%%%: 5700-block  0
call    0 never executed
        -: 5701:    {
       27: 5702:	SV*	data_sv = ST(0)
        -: 5703:;
       27: 5704:	SV*	formula_sv = ST(1)
       27: 5704-block  0
call    0 returned 27
        -: 5705:;
        -: 5706:	SV *	RETVAL;
        -: 5707:#line 5135 "LikeR.xs"
        -: 5708:	{
        -: 5709:		const char *restrict formula = SvPV_nolen(formula_sv);
        -: 5710:		char f_cpy[512];
        -: 5711:		char *restrict src, *restrict dst, *restrict tilde, *restrict lhs, *restrict rhs, *restrict chunk;
        -: 5712:
        -: 5713:		char **restrict terms = NULL, **restrict uniq_terms = NULL, **restrict exp_terms = NULL, **restrict parent_term = NULL;
        -: 5714:		bool *restrict is_dummy = NULL, *is_interact = NULL;
        -: 5715:		char **restrict dummy_base = NULL, **restrict dummy_level = NULL;
        -: 5716:		int *restrict term_map = NULL, *restrict left_idx = NULL, *restrict right_idx = NULL;
        -: 5717:		unsigned int term_cap = 64, exp_cap = 64, num_terms = 0, num_uniq = 0, p = 0, p_exp = 0;
        -: 5718:		size_t n = 0, valid_n = 0, i, j;
        -: 5719:		bool has_intercept = true;
        -: 5720:
        -: 5721:		char **restrict row_names = NULL;
        -: 5722:		HV **restrict row_hashes = NULL;
        -: 5723:		HV *restrict data_hoa = NULL;
        -: 5724:		SV *restrict ref = NULL;
        -: 5725:		HE *restrict entry;
        -: 5726:		double **restrict X_mat = NULL;
        -: 5727:		double *restrict Y = NULL;
        -: 5728:
        -: 5729:		if (!SvROK(data_sv)) croak("aov: data is required and must be a reference");
        -: 5730:
        -: 5731:		// ========================================================================
        -: 5732:		// PHASE 1: Data Extraction
        -: 5733:		// ========================================================================
        -: 5734:		ref = SvRV(data_sv);
        -: 5735:		if (SvTYPE(ref) == SVt_PVHV) {
        -: 5736:			HV*restrict hv = (HV*)ref;
        -: 5737:			if (hv_iterinit(hv) == 0) croak("aov: Data hash is empty");
        -: 5738:			entry = hv_iternext(hv);
        -: 5739:			if (entry) {
        -: 5740:				 SV*restrict val = hv_iterval(hv, entry);
        -: 5741:				 if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
        -: 5742:				     data_hoa = hv;
        -: 5743:				     n = av_len((AV*)SvRV(val)) + 1;
        -: 5744:				     Newx(row_names, n, char*);
        -: 5745:				     for(i = 0; i < n; i++) { 
        -: 5746:				         char buf[32]; snprintf(buf, sizeof(buf), "%lu", (unsigned long)(i+1));
        -: 5747:				         row_names[i] = savepv(buf); 
        -: 5748:				     }
        -: 5749:				 } else if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVHV) {
        -: 5750:				     n = hv_iterinit(hv);
        -: 5751:				     Newx(row_names, n, char*); Newx(row_hashes, n, HV*);
        -: 5752:				     i = 0;
        -: 5753:				     while ((entry = hv_iternext(hv))) {
        -: 5754:				         unsigned int len;
        -: 5755:				         row_names[i] = savepv(hv_iterkey(entry, &len));
        -: 5756:				         row_hashes[i] = (HV*)SvRV(hv_iterval(hv, entry));
        -: 5757:				         i++;
        -: 5758:				     }
        -: 5759:				 } else croak("aov: Hash values must be ArrayRefs (HoA) or HashRefs (HoH)");
        -: 5760:			}
        -: 5761:		} else if (SvTYPE(ref) == SVt_PVAV) {
        -: 5762:			AV*restrict av = (AV*)ref;
        -: 5763:			n = av_len(av) + 1;
        -: 5764:			Newx(row_names, n, char*);
        -: 5765:			Newx(row_hashes, n, HV*);
        -: 5766:			for (i = 0; i < n; i++) {
        -: 5767:				 SV**restrict val = av_fetch(av, i, 0);
        -: 5768:				 if (val && SvROK(*val) && SvTYPE(SvRV(*val)) == SVt_PVHV) {
        -: 5769:				     row_hashes[i] = (HV*)SvRV(*val);
        -: 5770:				     char buf[32];
        -: 5771:				     snprintf(buf, sizeof(buf), "%lu", (unsigned long)(i + 1));
        -: 5772:				     row_names[i] = savepv(buf);
        -: 5773:				 } else {
        -: 5774:				     for (size_t k = 0; k < i; k++) Safefree(row_names[k]);
        -: 5775:				     Safefree(row_names); Safefree(row_hashes);
        -: 5776:				     croak("aov: Array values must be HashRefs (AoH)");
        -: 5777:				 }
        -: 5778:			}
        -: 5779:		} else croak("aov: Data must be an Array or Hash reference");
        -: 5780:
        -: 5781:		// ========================================================================
        -: 5782:		// PHASE 2: Formula Parsing & `.` Expansion
        -: 5783:		// ========================================================================
        -: 5784:		src = (char*)formula; dst = f_cpy;
        -: 5785:		while (*src && (dst - f_cpy < 511)) { if (!isspace(*src)) { *dst++ = *src; } src++; }
        -: 5786:		*dst = '\0';
        -: 5787:
        -: 5788:		tilde = strchr(f_cpy, '~');
        -: 5789:		if (!tilde) {
        -: 5790:			for (i = 0; i < n; i++) Safefree(row_names[i]);
        -: 5791:			Safefree(row_names); if (row_hashes) Safefree(row_hashes);
        -: 5792:			croak("aov: invalid formula, missing '~'");
        -: 5793:		}
        -: 5794:		*tilde = '\0';
        -: 5795:		lhs = f_cpy;
        -: 5796:		rhs = tilde + 1;
        -: 5797:
        -: 5798:		char *restrict p_idx;
        -: 5799:		while ((p_idx = strstr(rhs, "-1")) != NULL) { has_intercept = false; memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1); }
        -: 5800:		while ((p_idx = strstr(rhs, "+0")) != NULL) { has_intercept = false; memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1); }
        -: 5801:		while ((p_idx = strstr(rhs, "0+")) != NULL) { has_intercept = false; memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1); }
        -: 5802:		if (rhs[0] == '0' && rhs[1] == '\0')        { has_intercept = false; rhs[0] = '\0'; }
        -: 5803:		while ((p_idx = strstr(rhs, "+1")) != NULL) { memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1); }
        -: 5804:		if (rhs[0] == '1' && rhs[1] == '\0')        { rhs[0] = '\0'; } 
        -: 5805:		else if (rhs[0] == '1' && rhs[1] == '+')    { memmove(rhs, rhs + 2, strlen(rhs + 2) + 1); }
        -: 5806:
        -: 5807:		while ((p_idx = strstr(rhs, "++")) != NULL) memmove(p_idx, p_idx + 1, strlen(p_idx + 1) + 1);
        -: 5808:		if (rhs[0] == '+') memmove(rhs, rhs + 1, strlen(rhs + 1) + 1);
        -: 5809:		size_t len_rhs = strlen(rhs);
        -: 5810:		if (len_rhs > 0 && rhs[len_rhs - 1] == '+') rhs[len_rhs - 1] = '\0';
        -: 5811:
        -: 5812:		char rhs_expanded[2048] = "";
        -: 5813:		size_t rhs_len = 0;
        -: 5814:		chunk = strtok(rhs, "+");
        -: 5815:		while (chunk != NULL) {
        -: 5816:			if (strcmp(chunk, ".") == 0) {
        -: 5817:				 AV *restrict cols = get_all_columns(data_hoa, row_hashes, n);
        -: 5818:				 for (size_t c = 0; c <= av_len(cols); c++) {
        -: 5819:				     SV **restrict col_sv = av_fetch(cols, c, 0);
        -: 5820:				     if (col_sv && SvOK(*col_sv)) {
        -: 5821:				         const char *restrict col_name = SvPV_nolen(*col_sv);
        -: 5822:				         if (strcmp(col_name, lhs) != 0) {
        -: 5823:				             size_t slen = strlen(col_name);
        -: 5824:				             if (rhs_len + slen + 2 < sizeof(rhs_expanded)) {
        -: 5825:				                 if (rhs_len > 0) { strcat(rhs_expanded, "+"); rhs_len++; }
        -: 5826:				                 strcat(rhs_expanded, col_name);
        -: 5827:				                 rhs_len += slen;
        -: 5828:				             }
        -: 5829:				         }
        -: 5830:				     }
        -: 5831:				 }
        -: 5832:				 SvREFCNT_dec(cols);
        -: 5833:			} else {
        -: 5834:				 size_t slen = strlen(chunk);
        -: 5835:				 if (rhs_len + slen + 2 < sizeof(rhs_expanded)) {
        -: 5836:				     if (rhs_len > 0) { strcat(rhs_expanded, "+"); rhs_len++; }
        -: 5837:				     strcat(rhs_expanded, chunk);
        -: 5838:				     rhs_len += slen;
        -: 5839:				 }
        -: 5840:			}
        -: 5841:			chunk = strtok(NULL, "+");
        -: 5842:		}
        -: 5843:
        -: 5844:		// Setup arrays safely
        -: 5845:		Newx(terms, term_cap, char*);
        -: 5846:		Newx(uniq_terms, term_cap, char*);
        -: 5847:		Newx(exp_terms, exp_cap, char*); Newx(parent_term, exp_cap, char*);
        -: 5848:		Newx(is_dummy, exp_cap, bool); Newx(is_interact, exp_cap, bool);
        -: 5849:		Newx(dummy_base, exp_cap, char*); Newx(dummy_level, exp_cap, char*);
        -: 5850:		Newx(term_map, exp_cap, int); Newx(left_idx, exp_cap, int); Newx(right_idx, exp_cap, int);
        -: 5851:
        -: 5852:		if (has_intercept) { terms[num_terms++] = savepv("Intercept"); }
        -: 5853:
        -: 5854:		if (strlen(rhs_expanded) > 0) {
        -: 5855:			chunk = strtok(rhs_expanded, "+");
        -: 5856:			while (chunk != NULL) {
        -: 5857:				 if (num_terms >= term_cap - 3) {
        -: 5858:				     term_cap *= 2;
        -: 5859:				     Renew(terms, term_cap, char*); Renew(uniq_terms, term_cap, char*);
        -: 5860:				 }
        -: 5861:				 char *restrict star = strchr(chunk, '*');
        -: 5862:				 if (star) {
        -: 5863:				     *star = '\0';
        -: 5864:				     char *restrict left = chunk;
        -: 5865:				     char *right = star + 1;
        -: 5866:				     char *restrict c_l = strchr(left, '^');
        -: 5867:				     if (c_l && strncmp(left, "I(", 2) != 0) *c_l = '\0';
        -: 5868:				     char *restrict c_r = strchr(right, '^'); if (c_r && strncmp(right, "I(", 2) != 0) *c_r = '\0';
        -: 5869:				     terms[num_terms++] = savepv(left);
        -: 5870:				     terms[num_terms++] = savepv(right);
        -: 5871:				     size_t inter_len = strlen(left) + strlen(right) + 2;
        -: 5872:				     terms[num_terms] = (char*)safemalloc(inter_len);
        -: 5873:				     snprintf(terms[num_terms++], inter_len, "%s:%s", left, right);
        -: 5874:				 } else {
        -: 5875:				     char *restrict c_chunk = strchr(chunk, '^'); 
        -: 5876:				     if (c_chunk && strncmp(chunk, "I(", 2) != 0) *c_chunk = '\0';
        -: 5877:				     terms[num_terms++] = savepv(chunk);
        -: 5878:				 }
        -: 5879:				 chunk = strtok(NULL, "+");
        -: 5880:			}
        -: 5881:		}
        -: 5882:
        -: 5883:		for (i = 0; i < num_terms; i++) {
        -: 5884:			bool found = false;
        -: 5885:			for (size_t k = 0; k < num_uniq; k++) {
        -: 5886:				  if (strcmp(terms[i], uniq_terms[k]) == 0) { found = true; break; }
        -: 5887:			}
        -: 5888:			if (!found) uniq_terms[num_uniq++] = savepv(terms[i]);
        -: 5889:		}
        -: 5890:		p = num_uniq;
        -: 5891:
        -: 5892:		// ========================================================================
        -: 5893:		// PHASE 3: Categorical & Interaction Expansion
        -: 5894:		// ========================================================================
        -: 5895:		for (j = 0; j < p; j++) {
        -: 5896:			if (p_exp + 64 >= exp_cap) {
        -: 5897:				  exp_cap *= 2;
        -: 5898:				  Renew(exp_terms, exp_cap, char*); Renew(parent_term, exp_cap, char*);
        -: 5899:				  Renew(is_dummy, exp_cap, bool); Renew(is_interact, exp_cap, bool);
        -: 5900:				  Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 5901:				  Renew(term_map, exp_cap, int); Renew(left_idx, exp_cap, int); Renew(right_idx, exp_cap, int);
        -: 5902:			}
        -: 5903:
        -: 5904:			if (strcmp(uniq_terms[j], "Intercept") == 0) {
        -: 5905:				  exp_terms[p_exp] = savepv("Intercept");
        -: 5906:				  parent_term[p_exp] = savepv("Intercept");
        -: 5907:				  is_dummy[p_exp] = false; is_interact[p_exp] = false;
        -: 5908:				  term_map[p_exp] = j;
        -: 5909:				  p_exp++;
        -: 5910:				  continue;
        -: 5911:			}
        -: 5912:
        -: 5913:			char *restrict colon = strchr(uniq_terms[j], ':');
        -: 5914:			if (colon) {
        -: 5915:				  char left[256], right[256];
        -: 5916:				  strncpy(left, uniq_terms[j], colon - uniq_terms[j]);
        -: 5917:				  left[colon - uniq_terms[j]] = '\0';
        -: 5918:				  strcpy(right, colon + 1);
        -: 5919:
        -: 5920:				  int *restrict l_indices = (int*)safemalloc(p_exp * sizeof(int)); int l_count = 0;
        -: 5921:				  int *restrict r_indices = (int*)safemalloc(p_exp * sizeof(int)); int r_count = 0;
        -: 5922:				  for (size_t e = 0; e < p_exp; e++) {
        -: 5923:				      if (strcmp(parent_term[e], left) == 0) l_indices[l_count++] = e;
        -: 5924:				      if (strcmp(parent_term[e], right) == 0) r_indices[r_count++] = e;
        -: 5925:				  }
        -: 5926:
        -: 5927:				  if (l_count == 0 || r_count == 0) {
        -: 5928:				      Safefree(l_indices); Safefree(r_indices);
        -: 5929:				      croak("aov: Interaction term '%s' requires its main effects to be explicitly included in the formula", uniq_terms[j]);
        -: 5930:				  } else {
        -: 5931:				      for (int li = 0; li < l_count; li++) {
        -: 5932:				          for (int ri = 0; ri < r_count; ri++) {
        -: 5933:				              if (p_exp >= exp_cap) {
        -: 5934:				                  exp_cap *= 2;
        -: 5935:				                  Renew(exp_terms, exp_cap, char*); Renew(parent_term, exp_cap, char*);
        -: 5936:				                  Renew(is_dummy, exp_cap, bool); Renew(is_interact, exp_cap, bool);
        -: 5937:				                  Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 5938:				                  Renew(term_map, exp_cap, int); Renew(left_idx, exp_cap, int); Renew(right_idx, exp_cap, int);
        -: 5939:				              }
        -: 5940:				              size_t t_len = strlen(exp_terms[l_indices[li]]) + strlen(exp_terms[r_indices[ri]]) + 2;
        -: 5941:				              exp_terms[p_exp] = (char*)safemalloc(t_len);
        -: 5942:				              snprintf(exp_terms[p_exp], t_len, "%s:%s", exp_terms[l_indices[li]], exp_terms[r_indices[ri]]);
        -: 5943:				              parent_term[p_exp] = savepv(uniq_terms[j]);
        -: 5944:				              is_dummy[p_exp] = false; is_interact[p_exp] = true;
        -: 5945:				              left_idx[p_exp] = l_indices[li];
        -: 5946:				              right_idx[p_exp] = r_indices[ri];
        -: 5947:				              term_map[p_exp] = j;
        -: 5948:				              p_exp++;
        -: 5949:				          }
        -: 5950:				      }
        -: 5951:				  }
        -: 5952:				  Safefree(l_indices); Safefree(r_indices);
        -: 5953:			} else {
        -: 5954:				  if (is_column_categorical(data_hoa, row_hashes, n, uniq_terms[j])) {
        -: 5955:				      char **restrict levels = NULL;
        -: 5956:				      unsigned int num_levels = 0, levels_cap = 8;
        -: 5957:				      Newx(levels, levels_cap, char*);
        -: 5958:				      for (i = 0; i < n; i++) {
        -: 5959:				          char* str_val = get_data_string_alloc(data_hoa, row_hashes, i, uniq_terms[j]);
        -: 5960:				          if (str_val) {
        -: 5961:				              bool found = false;
        -: 5962:				              for (size_t l = 0; l < num_levels; l++) {
        -: 5963:				                  if (strcmp(levels[l], str_val) == 0) { found = true; break; }
        -: 5964:				              }
        -: 5965:				              if (!found) {
        -: 5966:				                  if (num_levels >= levels_cap) { levels_cap *= 2; Renew(levels, levels_cap, char*); }
        -: 5967:				                  levels[num_levels++] = savepv(str_val);
        -: 5968:				              }
        -: 5969:				              Safefree(str_val);
        -: 5970:				          }
        -: 5971:				      }
        -: 5972:
        -: 5973:				      if (num_levels > 0) {
        -: 5974:				          for (size_t l1 = 0; l1 < num_levels - 1; l1++) {
        -: 5975:				              for (size_t l2 = l1 + 1; l2 < num_levels; l2++) {
        -: 5976:				                  if (strcmp(levels[l1], levels[l2]) > 0) {
        -: 5977:				                      char *tmp = levels[l1]; levels[l1] = levels[l2]; levels[l2] = tmp;
        -: 5978:				                  }
        -: 5979:				              }
        -: 5980:				          }
        -: 5981:				          for (size_t l = 1; l < num_levels; l++) {
        -: 5982:				              if (p_exp >= exp_cap) {
        -: 5983:				                  exp_cap *= 2;
        -: 5984:				                  Renew(exp_terms, exp_cap, char*); Renew(parent_term, exp_cap, char*);
        -: 5985:				                  Renew(is_dummy, exp_cap, bool); Renew(is_interact, exp_cap, bool);
        -: 5986:				                  Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 5987:				                  Renew(term_map, exp_cap, int); Renew(left_idx, exp_cap, int); Renew(right_idx, exp_cap, int);
        -: 5988:				              }
        -: 5989:				              size_t t_len = strlen(uniq_terms[j]) + strlen(levels[l]) + 1;
        -: 5990:				              exp_terms[p_exp] = (char*)safemalloc(t_len);
        -: 5991:				              snprintf(exp_terms[p_exp], t_len, "%s%s", uniq_terms[j], levels[l]);
        -: 5992:				              parent_term[p_exp] = savepv(uniq_terms[j]);
        -: 5993:				              is_dummy[p_exp] = true; is_interact[p_exp] = false;
        -: 5994:				              dummy_base[p_exp] = savepv(uniq_terms[j]);
        -: 5995:				              dummy_level[p_exp] = savepv(levels[l]);
        -: 5996:				              term_map[p_exp] = j;
        -: 5997:				              p_exp++;
        -: 5998:				          }
        -: 5999:				          for (size_t l = 0; l < num_levels; l++) Safefree(levels[l]);
        -: 6000:				          Safefree(levels);
        -: 6001:				      } else {
        -: 6002:				          Safefree(levels);
        -: 6003:				          exp_terms[p_exp] = savepv(uniq_terms[j]);
        -: 6004:				          parent_term[p_exp] = savepv(uniq_terms[j]);
        -: 6005:				          is_dummy[p_exp] = false; is_interact[p_exp] = false;
        -: 6006:				          term_map[p_exp] = j;
        -: 6007:				          p_exp++;
        -: 6008:				      }
        -: 6009:				  } else {
        -: 6010:				      exp_terms[p_exp] = savepv(uniq_terms[j]);
        -: 6011:				      parent_term[p_exp] = savepv(uniq_terms[j]);
        -: 6012:				      is_dummy[p_exp] = false; is_interact[p_exp] = false;
        -: 6013:				      term_map[p_exp] = j;
        -: 6014:				      p_exp++;
        -: 6015:				  }
        -: 6016:			}
        -: 6017:		}
        -: 6018:		X_mat = (double**)safemalloc(n * sizeof(double*));
        -: 6019:		for(i = 0; i < n; i++) X_mat[i] = (double*)safemalloc(p_exp * sizeof(double));
        -: 6020:		Newx(Y, n, double);
        -: 6021:		// ========================================================================
        -: 6022:		// PHASE 4: Matrix Construction & Listwise Deletion
        -: 6023:		// ========================================================================
        -: 6024:		for (i = 0; i < n; i++) {
        -: 6025:			double y_val = evaluate_term(data_hoa, row_hashes, i, lhs);
        -: 6026:			if (isnan(y_val)) { Safefree(row_names[i]); continue; }
        -: 6027:			bool row_ok = true;
        -: 6028:			double *restrict row_x = (double*)safemalloc(p_exp * sizeof(double));
        -: 6029:			for (j = 0; j < p_exp; j++) {
        -: 6030:				  if (strcmp(exp_terms[j], "Intercept") == 0) {
        -: 6031:				      row_x[j] = 1.0;
        -: 6032:				  } else if (is_interact[j]) {
        -: 6033:				      row_x[j] = row_x[left_idx[j]] * row_x[right_idx[j]];
        -: 6034:				  } else if (is_dummy[j]) {
        -: 6035:				      char*restrict str_val = get_data_string_alloc(data_hoa, row_hashes, i, dummy_base[j]);
        -: 6036:				      if (str_val) {
        -: 6037:				          row_x[j] = (strcmp(str_val, dummy_level[j]) == 0) ? 1.0 : 0.0;
        -: 6038:				          Safefree(str_val);
        -: 6039:				      } else { row_ok = false; break; }
        -: 6040:				  } else {
        -: 6041:				      row_x[j] = evaluate_term(data_hoa, row_hashes, i, parent_term[j]);
        -: 6042:				      if (isnan(row_x[j])) { row_ok = false; break; }
        -: 6043:				  }
        -: 6044:			}
        -: 6045:			if (!row_ok) { Safefree(row_names[i]); Safefree(row_x); continue; }
        -: 6046:
        -: 6047:			Y[valid_n] = y_val;
        -: 6048:			for (j = 0; j < p_exp; j++) X_mat[valid_n][j] = row_x[j];
        -: 6049:			valid_n++;
        -: 6050:			Safefree(row_x);
        -: 6051:			Safefree(row_names[i]);
        -: 6052:		}
        -: 6053:		Safefree(row_names);
        -: 6054:		if (valid_n <= p_exp) {
        -: 6055:			// Full Clean Up 
        -: 6056:			for (i = 0; i < num_terms; i++) Safefree(terms[i]); Safefree(terms);
        -: 6057:			for (i = 0; i < num_uniq; i++) Safefree(uniq_terms[i]); Safefree(uniq_terms);
        -: 6058:			for (j = 0; j < p_exp; j++) {
        -: 6059:				 Safefree(exp_terms[j]); Safefree(parent_term[j]);
        -: 6060:				 if (is_dummy[j]) { Safefree(dummy_base[j]); Safefree(dummy_level[j]); }
        -: 6061:			}
        -: 6062:			Safefree(exp_terms); Safefree(parent_term); 
        -: 6063:			Safefree(is_dummy); Safefree(is_interact); 
        -: 6064:			Safefree(dummy_base); Safefree(dummy_level);
        -: 6065:			Safefree(term_map); Safefree(left_idx); Safefree(right_idx);
        -: 6066:			for(i = 0; i < n; i++) Safefree(X_mat[i]);
        -: 6067:			Safefree(X_mat); Safefree(Y);
        -: 6068:			if (row_hashes) Safefree(row_hashes);
        -: 6069:			croak("aov: 0 degrees of freedom (too many NAs or parameters > observations)");
        -: 6070:		}
        -: 6071:		// ========================================================================
        -: 6072:		// PHASE 5: Math & Output Formatting
        -: 6073:		// ========================================================================
        -: 6074:		bool *restrict aliased_qr = (bool*)safemalloc(p_exp * sizeof(bool));
        -: 6075:		size_t *restrict rank_map = (size_t*)safemalloc(p_exp * sizeof(size_t));
        -: 6076:		apply_householder_aov(X_mat, Y, valid_n, p_exp, aliased_qr, rank_map);
        -: 6077:		double *restrict term_ss;
        -: 6078:		int *restrict term_df;
        -: 6079:		Newxz(term_ss, num_uniq, double);
        -: 6080:		Newxz(term_df, num_uniq, int);
        -: 6081:
        -: 6082:		for (i = 0; i < p_exp; i++) {
        -: 6083:			if (strcmp(exp_terms[i], "Intercept") == 0) continue; 
        -: 6084:			if (aliased_qr[i]) continue;
        -: 6085:			int t_idx = term_map[i];
        -: 6086:			size_t r_k = rank_map[i];
        -: 6087:			term_ss[t_idx] += Y[r_k] * Y[r_k];
        -: 6088:			term_df[t_idx] += 1;
        -: 6089:		}
        -: 6090:		int rank = 0;
        -: 6091:		for (i = 0; i < p_exp; i++) {
        -: 6092:			if (!aliased_qr[i]) rank++;
        -: 6093:		}
        -: 6094:		double rss_prev = 0.0;
        -: 6095:		for (i = rank; i < valid_n; i++) {
        -: 6096:			rss_prev += Y[i] * Y[i];
        -: 6097:		}
        -: 6098:		int res_df = valid_n - rank;
        -: 6099:		double ms_res = (res_df > 0) ? rss_prev / res_df : 0.0;
        -: 6100:
        -: 6101:		HV*restrict ret_hash = newHV();
        -: 6102:		for (j = 0; j < num_uniq; j++) {
        -: 6103:			if (strcmp(uniq_terms[j], "Intercept") == 0) continue;
        -: 6104:			HV*restrict term_stats = newHV();
        -: 6105:			double ss = term_ss[j];
        -: 6106:			int df = term_df[j];
        -: 6107:			double ms = (df > 0) ? ss / df : 0.0;
        -: 6108:
        -: 6109:			hv_stores(term_stats, "Df", newSViv(df));
        -: 6110:			hv_stores(term_stats, "Sum Sq", newSVnv(ss));
        -: 6111:			hv_stores(term_stats, "Mean Sq", newSVnv(ms));
        -: 6112:			if (ms_res > 0.0 && df > 0) {
        -: 6113:				  double f_val = ms / ms_res;
        -: 6114:				  hv_stores(term_stats, "F value", newSVnv(f_val));
        -: 6115:				  hv_stores(term_stats, "Pr(>F)", newSVnv(1.0 - pf(f_val, (double)df, (double)res_df)));
        -: 6116:			} else {
        -: 6117:				  hv_stores(term_stats, "F value", newSVnv(NAN));
        -: 6118:				  hv_stores(term_stats, "Pr(>F)", newSVnv(NAN));
        -: 6119:			}
        -: 6120:			hv_store(ret_hash, uniq_terms[j], strlen(uniq_terms[j]), newRV_noinc((SV*)term_stats), 0);
        -: 6121:		}
        -: 6122:
        -: 6123:		HV*restrict res_stats = newHV();
        -: 6124:		hv_stores(res_stats, "Df", newSViv(res_df));
        -: 6125:		hv_stores(res_stats, "Sum Sq", newSVnv(rss_prev));
        -: 6126:		hv_stores(res_stats, "Mean Sq", newSVnv(ms_res));
        -: 6127:		hv_stores(ret_hash, "Residuals", newRV_noinc((SV*)res_stats));
        -: 6128:
        -: 6129:		// Deep Cleanup
        -: 6130:		for (i = 0; i < num_terms; i++) Safefree(terms[i]); Safefree(terms);
        -: 6131:		for (i = 0; i < num_uniq; i++) Safefree(uniq_terms[i]); Safefree(uniq_terms);
        -: 6132:		for (j = 0; j < p_exp; j++) {
        -: 6133:			Safefree(exp_terms[j]); Safefree(parent_term[j]);
        -: 6134:			if (is_dummy[j]) { Safefree(dummy_base[j]); Safefree(dummy_level[j]); }
        -: 6135:		}
        -: 6136:		Safefree(exp_terms); Safefree(parent_term); 
        -: 6137:		Safefree(is_dummy); Safefree(is_interact); 
        -: 6138:		Safefree(dummy_base); Safefree(dummy_level);
        -: 6139:		Safefree(term_map); Safefree(left_idx); Safefree(right_idx);
        -: 6140:		Safefree(term_ss); Safefree(term_df);
        -: 6141:		for (i = 0; i < n; i++) Safefree(X_mat[i]);
        -: 6142:		Safefree(X_mat); Safefree(Y);
        -: 6143:		Safefree(aliased_qr); Safefree(rank_map);
        -: 6144:		if (row_hashes) Safefree(row_hashes);
        -: 6145:		RETVAL = newRV_noinc((SV*)ret_hash);
        -: 6146:	}
        -: 6147:#line 6148 "LikeR.c"
       18: 6148:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 18
       18: 6149:	ST(0) = RETVAL;
        -: 6150:    }
       18: 6151:    XSRETURN(1);
        -: 6152:}
        -: 6153:
        -: 6154:
        -: 6155:XS_EUPXS(XS_Stats__LikeR_fisher_test); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_fisher_test called 18 returned 83% blocks executed 89%
       18: 6156:XS_EUPXS(XS_Stats__LikeR_fisher_test)
        -: 6157:{
       18: 6158:    dVAR; dXSARGS;
       18: 6158-block  0
call    0 returned 18
branch  1 taken 3 (fallthrough)
branch  2 taken 15
        -: 6159:    PERL_UNUSED_VAR(cv); /* -W */
        -: 6160:    PERL_UNUSED_VAR(items); /* -W */
        -: 6161:    {
        -: 6162:	SV *	RETVAL;
        -: 6163:#line 5581 "LikeR.xs"
        -: 6164:{
        -: 6165:	if (items < 1) croak("fisher_test requires at least a data reference");
        -: 6166:
        -: 6167:	SV*restrict data_ref = ST(0);
        -: 6168:	double conf_level = 0.95;
        -: 6169:	const char*restrict alternative = "two.sided";
        -: 6170:
        -: 6171:	// Parse named arguments
        -: 6172:	for (unsigned short int i = 1; i < items; i += 2) {
        -: 6173:		if (i + 1 >= items) croak("fisher_test: odd number of arguments");
        -: 6174:		const char*restrict key = SvPV_nolen(ST(i));
        -: 6175:		SV*restrict val = ST(i + 1);
        -: 6176:		if (strEQ(key, "conf_level") || strEQ(key, "conf.level")) {
        -: 6177:			conf_level = SvNV(val);
        -: 6178:		} else if (strEQ(key, "alternative")) {
        -: 6179:			alternative = SvPV_nolen(val);
        -: 6180:		}
        -: 6181:	}
        -: 6182:
        -: 6183:	if (!SvROK(data_ref)) croak("fisher_test requires a reference to an Array or Hash");
        -: 6184:	SV*restrict deref = SvRV(data_ref);
        -: 6185:	size_t a = 0, b = 0, c = 0, d = 0;
        -: 6186:	// Extract Data
        -: 6187:	if (SvTYPE(deref) == SVt_PVAV) {
        -: 6188:	  AV*restrict outer = (AV*)deref;
        -: 6189:	  if (av_len(outer) != 1) croak("Outer array must have exactly 2 rows");
        -: 6190:	  SV**restrict row1_ptr = av_fetch(outer, 0, 0);
        -: 6191:	  SV**restrict row2_ptr = av_fetch(outer, 1, 0);
        -: 6192:	  if (row1_ptr && row2_ptr && SvROK(*row1_ptr) && SvROK(*row2_ptr)) {
        -: 6193:		   AV*restrict row1 = (AV*)SvRV(*row1_ptr);
        -: 6194:		   AV*restrict row2 = (AV*)SvRV(*row2_ptr);
        -: 6195:		   SV**restrict a_ptr = av_fetch(row1, 0, 0);
        -: 6196:		   SV**restrict b_ptr = av_fetch(row1, 1, 0);
        -: 6197:		   SV**restrict c_ptr = av_fetch(row2, 0, 0);
        -: 6198:		   SV**restrict d_ptr = av_fetch(row2, 1, 0);
        -: 6199:		   a = (a_ptr && SvOK(*a_ptr)) ? SvIV(*a_ptr) : 0;
        -: 6200:		   b = (b_ptr && SvOK(*b_ptr)) ? SvIV(*b_ptr) : 0;
        -: 6201:		   c = (c_ptr && SvOK(*c_ptr)) ? SvIV(*c_ptr) : 0;
        -: 6202:		   d = (d_ptr && SvOK(*d_ptr)) ? SvIV(*d_ptr) : 0;
        -: 6203:	  } else {
        -: 6204:		  croak("Invalid 2D Array structure");
        -: 6205:	  }
        -: 6206:	} else if (SvTYPE(deref) == SVt_PVHV) {
        -: 6207:		// Fixed 2D Hash Logic: Sort keys lexically to enforce structured rows/columns
        -: 6208:		HV*restrict outer = (HV*)deref;
        -: 6209:		if (hv_iterinit(outer) != 2) croak("Outer hash must have exactly 2 keys");
        -: 6210:		HE*restrict he1 = hv_iternext(outer);
        -: 6211:		HE*restrict he2 = hv_iternext(outer);
        -: 6212:		if (!he1 || !he2) croak("Invalid outer hash");
        -: 6213:		const char*restrict k1 = SvPV_nolen(hv_iterkeysv(he1));
        -: 6214:		const char*restrict k2 = SvPV_nolen(hv_iterkeysv(he2));
        -: 6215:		HE*restrict row1_he = (strcmp(k1, k2) < 0) ? he1 : he2;
        -: 6216:		HE*restrict row2_he = (strcmp(k1, k2) < 0) ? he2 : he1;
        -: 6217:		SV*restrict row1_sv = hv_iterval(outer, row1_he);
        -: 6218:		SV*restrict row2_sv = hv_iterval(outer, row2_he);
        -: 6219:		if (!SvROK(row1_sv) || SvTYPE(SvRV(row1_sv)) != SVt_PVHV ||
        -: 6220:			!SvROK(row2_sv) || SvTYPE(SvRV(row2_sv)) != SVt_PVHV) {
        -: 6221:			croak("Inner elements must be hashes");
        -: 6222:		}
        -: 6223:		HV*restrict in1 = (HV*)SvRV(row1_sv);
        -: 6224:		HV*restrict in2 = (HV*)SvRV(row2_sv);
        -: 6225:		if (hv_iterinit(in1) != 2 || hv_iterinit(in2) != 2) croak("Inner hashes must have exactly 2 keys");
        -: 6226:		HE*restrict in1_he1 = hv_iternext(in1);
        -: 6227:		HE*restrict in1_he2 = hv_iternext(in1);
        -: 6228:		const char*restrict in1_k1 = SvPV_nolen(hv_iterkeysv(in1_he1));
        -: 6229:		const char*restrict in1_k2 = SvPV_nolen(hv_iterkeysv(in1_he2));
        -: 6230:		HE*restrict in1_c1 = (strcmp(in1_k1, in1_k2) < 0) ? in1_he1 : in1_he2;
        -: 6231:		HE*restrict in1_c2 = (strcmp(in1_k1, in1_k2) < 0) ? in1_he2 : in1_he1;
        -: 6232:		HE*restrict in2_he1 = hv_iternext(in2);
        -: 6233:		HE*restrict in2_he2 = hv_iternext(in2);
        -: 6234:		const char*restrict in2_k1 = SvPV_nolen(hv_iterkeysv(in2_he1));
        -: 6235:		const char*restrict in2_k2 = SvPV_nolen(hv_iterkeysv(in2_he2));
        -: 6236:		HE*restrict in2_c1 = (strcmp(in2_k1, in2_k2) < 0) ? in2_he1 : in2_he2;
        -: 6237:		HE*restrict in2_c2 = (strcmp(in2_k1, in2_k2) < 0) ? in2_he2 : in2_he1;
        -: 6238:		a = (hv_iterval(in1, in1_c1) && SvOK(hv_iterval(in1, in1_c1))) ? SvIV(hv_iterval(in1, in1_c1)) : 0;
        -: 6239:		b = (hv_iterval(in1, in1_c2) && SvOK(hv_iterval(in1, in1_c2))) ? SvIV(hv_iterval(in1, in1_c2)) : 0;
        -: 6240:		c = (hv_iterval(in2, in2_c1) && SvOK(hv_iterval(in2, in2_c1))) ? SvIV(hv_iterval(in2, in2_c1)) : 0;
        -: 6241:		d = (hv_iterval(in2, in2_c2) && SvOK(hv_iterval(in2, in2_c2))) ? SvIV(hv_iterval(in2, in2_c2)) : 0;
        -: 6242:	} else {
        -: 6243:	  croak("Input must be a 2D Array or 2D Hash");
        -: 6244:	}
        -: 6245:
        -: 6246:	// Perform Calculations via Helpers
        -: 6247:	double p_val = exact_p_value(a, b, c, d, alternative);
        -: 6248:	double mle_or, ci_low, ci_high;
        -: 6249:	calculate_exact_stats(a, b, c, d, conf_level, alternative, &mle_or, &ci_low, &ci_high);
        -: 6250:
        -: 6251:	// Construct the Return HashRef purely in C
        -: 6252:	HV*restrict ret_hash = newHV();
        -: 6253:	hv_stores(ret_hash, "method", newSVpv("Fisher's Exact Test for Count Data", 0));
        -: 6254:	hv_stores(ret_hash, "alternative", newSVpv(alternative, 0));
        -: 6255:	AV*restrict ci_array = newAV();
        -: 6256:	av_push(ci_array, newSVnv(ci_low));
        -: 6257:	av_push(ci_array, newSVnv(ci_high));
        -: 6258:	hv_stores(ret_hash, "conf_int", newRV_noinc((SV*)ci_array));
        -: 6259:	HV*restrict est_hash = newHV();
        -: 6260:	hv_stores(ret_hash, "estimate", newRV_noinc((SV*)est_hash));
        -: 6261:	hv_stores(est_hash, "odds ratio", newSVnv(mle_or));
        -: 6262:	hv_stores(ret_hash, "p_value", newSVnv(p_val));
        -: 6263:	// Return the HashRef
        -: 6264:	RETVAL = newRV_noinc((SV*)ret_hash);
        -: 6265:}
        -: 6266:#line 6267 "LikeR.c"
       15: 6267:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 15
       15: 6268:	ST(0) = RETVAL;
        -: 6269:    }
       15: 6270:    XSRETURN(1);
        -: 6271:}
        -: 6272:
        -: 6273:
        -: 6274:XS_EUPXS(XS_Stats__LikeR_power_t_test); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_power_t_test called 21 returned 100% blocks executed 71%
       21: 6275:XS_EUPXS(XS_Stats__LikeR_power_t_test)
        -: 6276:{
       21: 6277:    dVAR; dXSARGS;
       21: 6277-block  0
call    0 returned 21
branch  1 taken 0 (fallthrough)
branch  2 taken 21
        -: 6278:    PERL_UNUSED_VAR(cv); /* -W */
        -: 6279:    PERL_UNUSED_VAR(items); /* -W */
        -: 6280:    {
        -: 6281:	SV *	RETVAL;
        -: 6282:#line 5688 "LikeR.xs"
        -: 6283:{
        -: 6284:    SV* sv_n = NULL;
        -: 6285:    SV* sv_delta = NULL;
        -: 6286:    SV* sv_sd = NULL;
        -: 6287:    SV* sv_sig_level = NULL;
        -: 6288:    SV* sv_power = NULL;
        -: 6289:
        -: 6290:    const char* restrict type = "two.sample";
        -: 6291:    const char* restrict alternative = "two.sided";
        -: 6292:    bool strict = false;
        -: 6293:    double tol = pow(2.2204460492503131e-16, 0.25); 
        -: 6294:
        -: 6295:    if (items % 2 != 0) croak("Usage: power_t_test(n => 30, delta => 0.5, sd => 1.0, ...)");
        -: 6296:    for (unsigned short int i = 0; i < items; i += 2) {
        -: 6297:        const char* restrict key = SvPV_nolen(ST(i));
        -: 6298:        SV* restrict val = ST(i+1);
        -: 6299:
        -: 6300:        if      (strEQ(key, "n"))           sv_n = val;
        -: 6301:        else if (strEQ(key, "delta"))       sv_delta = val;
        -: 6302:        else if (strEQ(key, "sd"))          sv_sd = val;
        -: 6303:        else if (strEQ(key, "sig.level") || strEQ(key, "sig_level")) sv_sig_level = val;
        -: 6304:        else if (strEQ(key, "power"))       sv_power = val;
        -: 6305:        else if (strEQ(key, "type"))        type = SvPV_nolen(val);
        -: 6306:        else if (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 6307:        else if (strEQ(key, "strict"))      strict = SvTRUE(val);
        -: 6308:        else if (strEQ(key, "tol"))         tol = SvNV(val);
        -: 6309:        else croak("power_t_test: unknown argument '%s'", key);
        -: 6310:    }
        -: 6311:
        -: 6312:    bool is_null_n = (!sv_n || !SvOK(sv_n));
        -: 6313:    bool is_null_delta = (!sv_delta || !SvOK(sv_delta));
        -: 6314:    bool is_null_power = (!sv_power || !SvOK(sv_power));
        -: 6315:    bool is_null_sd = (sv_sd && !SvOK(sv_sd)); 
        -: 6316:    bool is_null_sig_level = (sv_sig_level && !SvOK(sv_sig_level));
        -: 6317:
        -: 6318:    int missing_count = 0;
        -: 6319:    if (is_null_n) missing_count++;
        -: 6320:    if (is_null_delta) missing_count++;
        -: 6321:    if (is_null_power) missing_count++;
        -: 6322:    if (is_null_sd) missing_count++;
        -: 6323:    if (is_null_sig_level) missing_count++;
        -: 6324:
        -: 6325:    if (missing_count != 1) {
        -: 6326:        croak("power_t_test: exactly one of 'n', 'delta', 'sd', 'power', and 'sig_level' must be undef/NULL");
        -: 6327:    }
        -: 6328:
        -: 6329:    double n = is_null_n ? 0.0 : SvNV(sv_n);
        -: 6330:    double delta = is_null_delta ? 0.0 : SvNV(sv_delta);
        -: 6331:    double sd = (!sv_sd || is_null_sd) ? 1.0 : SvNV(sv_sd);
        -: 6332:    double sig_level = (!sv_sig_level || is_null_sig_level) ? 0.05 : SvNV(sv_sig_level);
        -: 6333:    double power = is_null_power ? 0.0 : SvNV(sv_power);
        -: 6334:    short int tsample = (strEQ(type, "one.sample") || strEQ(type, "paired")) ? 1 : 2;
        -: 6335:    short int tside = (strEQ(alternative, "one.sided") || strEQ(alternative, "greater") || strEQ(alternative, "less")) ? 1 : 2;
        -: 6336:    if (tside == 2 && !is_null_delta) delta = fabs(delta);
        -: 6337:    if (is_null_power) {
        -: 6338:        power = p_body(n, delta, sd, sig_level, tsample, tside, strict);
        -: 6339:    } else if (is_null_n) {
        -: 6340:        double low = 2.0, high = 1e7;
        -: 6341:        while (p_body(high, delta, sd, sig_level, tsample, tside, strict) < power && high < 1e12) high *= 2.0;
        -: 6342:        while (high - low > tol) {
        -: 6343:            double mid = low + (high - low) / 2.0;
        -: 6344:            if (p_body(mid, delta, sd, sig_level, tsample, tside, strict) < power) low = mid;
        -: 6345:            else high = mid;
        -: 6346:        }
        -: 6347:        n = low + (high - low) / 2.0;
        -: 6348:    } else if (is_null_sd) {
        -: 6349:        double low = delta * 1e-7, high = delta * 1e7;
        -: 6350:        while (high - low > tol) {
        -: 6351:            double mid = low + (high - low) / 2.0;
        -: 6352:            if (p_body(n, delta, mid, sig_level, tsample, tside, strict) > power) low = mid;
        -: 6353:            else high = mid;
        -: 6354:        }
        -: 6355:        sd = low + (high - low) / 2.0;
        -: 6356:    } else if (is_null_delta) {
        -: 6357:        double low = sd * 1e-7, high = sd * 1e7;
        -: 6358:        while (p_body(n, high, sd, sig_level, tsample, tside, strict) < power && high < 1e12) high *= 2.0;
        -: 6359:        while (high - low > tol) {
        -: 6360:            double mid = low + (high - low) / 2.0;
        -: 6361:            if (p_body(n, mid, sd, sig_level, tsample, tside, strict) < power) low = mid;
        -: 6362:            else high = mid;
        -: 6363:        }
        -: 6364:        delta = low + (high - low) / 2.0;
        -: 6365:    } else if (is_null_sig_level) {
        -: 6366:        double low = 1e-10, high = 1.0 - 1e-10;
        -: 6367:        while (high - low > tol) {
        -: 6368:            double mid = low + (high - low) / 2.0;
        -: 6369:            if (p_body(n, delta, sd, mid, tsample, tside, strict) < power) low = mid;
        -: 6370:            else high = mid;
        -: 6371:        }
        -: 6372:        sig_level = low + (high - low) / 2.0;
        -: 6373:    }
        -: 6374:    HV*restrict ret = newHV();
        -: 6375:    hv_stores(ret, "n", newSVnv(n));
        -: 6376:    hv_stores(ret, "delta", newSVnv(delta));
        -: 6377:    hv_stores(ret, "sd", newSVnv(sd));
        -: 6378:    hv_stores(ret, "sig.level", newSVnv(sig_level));
        -: 6379:    hv_stores(ret, "power", newSVnv(power));
        -: 6380:    hv_stores(ret, "alternative", newSVpv(alternative, 0));
        -: 6381:    const char*restrict m_str = (tsample == 1) ? (strEQ(type, "paired") ? "Paired t test power calculation" : "One-sample t test power calculation") : "Two-sample t test power calculation";
        -: 6382:    hv_stores(ret, "method", newSVpv(m_str, 0));
        -: 6383:    const char*restrict n_str = (tsample == 2) ? "n is number in *each* group" : (strEQ(type, "paired") ? "n is number of *pairs*, sd is std.dev. of *differences* within pairs" : "");
        -: 6384:    if (n_str[0] != '\0') hv_stores(ret, "note", newSVpv(n_str, 0));
        -: 6385:    RETVAL = newRV_noinc((SV*)ret);
        -: 6386:}
        -: 6387:#line 6388 "LikeR.c"
       21: 6388:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 21
       21: 6389:	ST(0) = RETVAL;
        -: 6390:    }
       21: 6391:    XSRETURN(1);
        -: 6392:}
        -: 6393:
        -: 6394:
        -: 6395:XS_EUPXS(XS_Stats__LikeR_kruskal_test); /* prototype to pass -Wmissing-prototypes */
function XS_Stats__LikeR_kruskal_test called 8 returned 100% blocks executed 88%
        8: 6396:XS_EUPXS(XS_Stats__LikeR_kruskal_test)
        -: 6397:{
        8: 6398:    dVAR; dXSARGS;
        8: 6398-block  0
call    0 returned 8
branch  1 taken 8 (fallthrough)
branch  2 taken 0
        -: 6399:    PERL_UNUSED_VAR(cv); /* -W */
        -: 6400:    PERL_UNUSED_VAR(items); /* -W */
        -: 6401:    {
        -: 6402:	SV *	RETVAL;
        -: 6403:#line 5797 "LikeR.xs"
        -: 6404:{
        -: 6405:	SV *restrict x_sv = NULL, *restrict g_sv = NULL, *restrict h_sv = NULL;
        -: 6406:	unsigned int arg_idx = 0;
        -: 6407:
        -: 6408:	// 1. Shift positional arguments
        -: 6409:	//    Accept either: (arrayref, arrayref) or (hashref)
        -: 6410:	if (arg_idx < items && SvROK(ST(arg_idx))) {
        -: 6411:		svtype t = SvTYPE(SvRV(ST(arg_idx)));
        -: 6412:		if (t == SVt_PVAV) {
        -: 6413:			x_sv = ST(arg_idx++);
        -: 6414:		} else if (t == SVt_PVHV) {
        -: 6415:			h_sv = ST(arg_idx++);          /* hash-of-arrays shortcut */
        -: 6416:		}
        -: 6417:	}
        -: 6418:	if (!h_sv && arg_idx < items
        -: 6419:	          && SvROK(ST(arg_idx))
        -: 6420:	          && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 6421:		g_sv = ST(arg_idx++);
        -: 6422:	}
        -: 6423:	// 2. Parse named arguments (fallback)
        -: 6424:	for (; arg_idx < items; arg_idx += 2) {
        -: 6425:		const char *restrict key = SvPV_nolen(ST(arg_idx));
        -: 6426:		SV         *restrict val = ST(arg_idx + 1);
        -: 6427:		if      (strEQ(key, "x")) x_sv = val;
        -: 6428:		else if (strEQ(key, "g")) g_sv = val;
        -: 6429:		else if (strEQ(key, "h")) h_sv = val;
        -: 6430:		else croak("kruskal_test: unknown argument '%s'", key);
        -: 6431:	}
        -: 6432:	// 3. Mutual-exclusion guard
        -: 6433:	if (h_sv && (x_sv || g_sv))
        -: 6434:		croak("kruskal_test: cannot mix 'h' (hash-of-arrays) with 'x'/'g' inputs");
        -: 6435:	/* ------------------------------------------------------------------ */
        -: 6436:	/* Shared state filled by whichever input branch runs                  */
        -: 6437:	/* ------------------------------------------------------------------ */
        -: 6438:	RankInfo *restrict ri = NULL;
        -: 6439:	size_t valid_n = 0;
        -: 6440:	size_t k       = 0;
        -: 6441:	/* ------------------------------------------------------------------ */
        -: 6442:	/* 4a. Hash-of-arrays input path                                       */
        -: 6443:	/*     my %x = ( group1 => [...], group2 => [...], ... )              */
        -: 6444:	/* ------------------------------------------------------------------ */
        -: 6445:	if (h_sv) {
        -: 6446:		if (!SvROK(h_sv) || SvTYPE(SvRV(h_sv)) != SVt_PVHV)
        -: 6447:			croak("kruskal_test: 'h' must be a HASH reference");
        -: 6448:		HV *restrict h_hv = (HV*)SvRV(h_sv);
        -: 6449:		// First pass – validate values and tally total elements
        -: 6450:		size_t total = 0;
        -: 6451:		hv_iterinit(h_hv);
        -: 6452:		HE *restrict he;
        -: 6453:		while ((he = hv_iternext(h_hv))) {
        -: 6454:			SV *restrict val = HeVAL(he);
        -: 6455:			if (!SvROK(val) || SvTYPE(SvRV(val)) != SVt_PVAV)
        -: 6456:				croak("kruskal_test: every value in 'h' must be an ARRAY reference");
        -: 6457:			total += (size_t)(av_len((AV*)SvRV(val)) + 1);
        -: 6458:		}
        -: 6459:		if (total < 2) croak("not enough observations");
        -: 6460:
        -: 6461:		ri = (RankInfo *)safemalloc(total * sizeof(RankInfo));
        -: 6462:
        -: 6463:		/* Second pass – fill ri[], assigning one group_id per hash key */
        -: 6464:		size_t group_id = 0;
        -: 6465:		hv_iterinit(h_hv);
        -: 6466:		while ((he = hv_iternext(h_hv))) {
        -: 6467:			AV    *restrict av  = (AV*)SvRV(HeVAL(he));
        -: 6468:			size_t          n_g = (size_t)(av_len(av) + 1);
        -: 6469:			for (size_t i = 0; i < n_g; i++) {
        -: 6470:				SV **restrict el = av_fetch(av, i, 0);
        -: 6471:				if (el && SvOK(*el) && looks_like_number(*el)) {
        -: 6472:					ri[valid_n].val = SvNV(*el);
        -: 6473:					ri[valid_n].idx = group_id;   /* group identity */
        -: 6474:					valid_n++;
        -: 6475:				}
        -: 6476:			}
        -: 6477:			group_id++;
        -: 6478:		}
        -: 6479:		k = group_id;   /* number of unique groups = number of hash keys */
        -: 6480:
        -: 6481:	/* ------------------------------------------------------------------
        -: 6482:	/* 4b. Original x / g array-pair input path (unchanged)
        -: 6483:	/* ------------------------------------------------------------------ */
        -: 6484:	} else {
        -: 6485:		if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 6486:			croak("kruskal_test: 'x' is a required argument and must be an ARRAY reference");
        -: 6487:		if (!g_sv || !SvROK(g_sv) || SvTYPE(SvRV(g_sv)) != SVt_PVAV)
        -: 6488:			croak("kruskal_test: 'g' is a required argument and must be an ARRAY reference");
        -: 6489:
        -: 6490:		AV *restrict x_av = (AV*)SvRV(x_sv);
        -: 6491:		AV *restrict g_av = (AV*)SvRV(g_sv);
        -: 6492:		size_t nx = (size_t)(av_len(x_av) + 1);
        -: 6493:		size_t ng = (size_t)(av_len(g_av) + 1);
        -: 6494:		if (nx != ng) croak("kruskal_test: 'x' and 'g' must have the same length");
        -: 6495:		if (nx < 2)   croak("not enough observations");
        -: 6496:
        -: 6497:		ri = (RankInfo *)safemalloc(nx * sizeof(RankInfo));
        -: 6498:
        -: 6499:		// Map string group names → contiguous integer IDs
        -: 6500:		HV    *restrict group_map    = newHV();
        -: 6501:		size_t          next_group_id = 0;
        -: 6502:
        -: 6503:		for (size_t i = 0; i < nx; i++) {
        -: 6504:			SV **restrict x_el = av_fetch(x_av, i, 0);
        -: 6505:			SV **restrict g_el = av_fetch(g_av, i, 0);
        -: 6506:			if (x_el && SvOK(*x_el) && looks_like_number(*x_el)
        -: 6507:			         && g_el && SvOK(*g_el)) {
        -: 6508:				const char *restrict g_str = SvPV_nolen(*g_el);
        -: 6509:				STRLEN                glen  = strlen(g_str);
        -: 6510:				SV   **restrict id_sv = hv_fetch(group_map, g_str, glen, 0);
        -: 6511:				size_t group_id;
        -: 6512:				if (id_sv) {
        -: 6513:					group_id = SvUV(*id_sv);
        -: 6514:				} else {
        -: 6515:					group_id = next_group_id++;
        -: 6516:					hv_store(group_map, g_str, glen, newSVuv(group_id), 0);
        -: 6517:				}
        -: 6518:				ri[valid_n].val = SvNV(*x_el);
        -: 6519:				ri[valid_n].idx = group_id;
        -: 6520:				valid_n++;
        -: 6521:			}
        -: 6522:		}
        -: 6523:		k = next_group_id;
        -: 6524:		SvREFCNT_dec(group_map);
        -: 6525:	}
        -: 6526:
        -: 6527:	/* ------------------------------------------------------------------ */
        -: 6528:	/* 5. Shared post-extraction validation                                */
        -: 6529:	/* ------------------------------------------------------------------ */
        -: 6530:	if (valid_n < 2) { Safefree(ri); croak("not enough observations");            }
        -: 6531:	if (k       < 2) { Safefree(ri); croak("all observations are in the same group"); }
        -: 6532:
        -: 6533:	// 6. Ranking and Tie Accumulation (Reusing LikeR Helper)
        -: 6534:	bool   has_ties = 0;
        -: 6535:	double tie_adj  = rank_and_count_ties(ri, valid_n, &has_ties);
        -: 6536:
        -: 6537:	// 7. Aggregate Sum of Ranks by Group (tapply(r, g, sum))
        -: 6538:	double *restrict group_rank_sums = (double *)safecalloc(k, sizeof(double));
        -: 6539:	size_t *restrict group_counts    = (size_t *)safecalloc(k, sizeof(size_t));
        -: 6540:	for (size_t i = 0; i < valid_n; i++) {
        -: 6541:		size_t g_id = ri[i].idx;
        -: 6542:		group_rank_sums[g_id] += ri[i].rank;
        -: 6543:		group_counts[g_id]++;
        -: 6544:	}
        -: 6545:
        -: 6546:	// 8. Calculate STATISTIC
        -: 6547:	double stat_base = 0.0;
        -: 6548:	for (size_t i = 0; i < k; i++) {
        -: 6549:		if (group_counts[i] > 0)
        -: 6550:			stat_base += (group_rank_sums[i] * group_rank_sums[i])
        -: 6551:			             / (double)group_counts[i];
        -: 6552:	}
        -: 6553:	Safefree(group_rank_sums); Safefree(group_counts); Safefree(ri);
        -: 6554:
        -: 6555:	double n_d  = (double)valid_n;
        -: 6556:	double stat = (12.0 * stat_base / (n_d * (n_d + 1.0))) - 3.0 * (n_d + 1.0);
        -: 6557:	if (tie_adj > 0.0) {
        -: 6558:		double tie_denom = 1.0 - (tie_adj / (n_d * n_d * n_d - n_d));
        -: 6559:		stat /= tie_denom;
        -: 6560:	}
        -: 6561:	int    df    = (int)k - 1;
        -: 6562:	double p_val = get_p_value(stat, df);
        -: 6563:
        -: 6564:	// 9. Return structured data exactly like R's htest
        -: 6565:	HV *restrict res = newHV();
        -: 6566:	hv_stores(res, "statistic", newSVnv(stat));
        -: 6567:	hv_stores(res, "parameter", newSViv(df));
        -: 6568:	hv_stores(res, "p_value",   newSVnv(p_val));
        -: 6569:	hv_stores(res, "p.value",   newSVnv(p_val));
        -: 6570:	hv_stores(res, "method",    newSVpv("Kruskal-Wallis rank sum test", 0));
        -: 6571:	RETVAL = newRV_noinc((SV*)res);
        -: 6572:}
        -: 6573:#line 6574 "LikeR.c"
        8: 6574:	RETVAL = sv_2mortal(RETVAL);
call    0 returned 8
        8: 6575:	ST(0) = RETVAL;
        -: 6576:    }
        8: 6577:    XSRETURN(1);
        -: 6578:}
        -: 6579:
        -: 6580:#ifdef __cplusplus
        -: 6581:extern "C" {
        -: 6582:#endif
        -: 6583:XS_EXTERNAL(boot_Stats__LikeR); /* prototype to pass -Wmissing-prototypes */
function boot_Stats__LikeR called 3 returned 100% blocks executed 100%
        3: 6584:XS_EXTERNAL(boot_Stats__LikeR)
        -: 6585:{
        -: 6586:#if PERL_VERSION_LE(5, 21, 5)
        -: 6587:    dVAR; dXSARGS;
        -: 6588:#else
        3: 6589:    dVAR; dXSBOOTARGSXSAPIVERCHK;
        3: 6589-block  0
call    0 returned 3
        -: 6590:#endif
        -: 6591:#if PERL_VERSION_LE(5, 8, 999) /* PERL_VERSION_LT is 5.33+ */
        -: 6592:    char* file = __FILE__;
        -: 6593:#else
        3: 6594:    const char* file = __FILE__;
        -: 6595:#endif
        -: 6596:
        -: 6597:    PERL_UNUSED_VAR(file);
        -: 6598:
        -: 6599:    PERL_UNUSED_VAR(cv); /* -W */
        -: 6600:    PERL_UNUSED_VAR(items); /* -W */
        -: 6601:#if PERL_VERSION_LE(5, 21, 5)
        -: 6602:    XS_VERSION_BOOTCHECK;
        -: 6603:#  ifdef XS_APIVERSION_BOOTCHECK
        -: 6604:    XS_APIVERSION_BOOTCHECK;
        -: 6605:#  endif
        -: 6606:#endif
        -: 6607:
        3: 6608:        newXS_deffile("Stats::LikeR::ks_test", XS_Stats__LikeR_ks_test);
call    0 returned 3
        3: 6609:        newXS_deffile("Stats::LikeR::wilcox_test", XS_Stats__LikeR_wilcox_test);
call    0 returned 3
        3: 6610:        newXS_deffile("Stats::LikeR::_chisq_c", XS_Stats__LikeR__chisq_c);
call    0 returned 3
        3: 6611:        (void)newXSproto_portable("Stats::LikeR::write_table", XS_Stats__LikeR_write_table, file, ";@");
call    0 returned 3
        3: 6612:        (void)newXSproto_portable("Stats::LikeR::_parse_csv_file", XS_Stats__LikeR__parse_csv_file, file, "$$$;$");
call    0 returned 3
        3: 6613:        (void)newXSproto_portable("Stats::LikeR::cov", XS_Stats__LikeR_cov, file, "$$;$");
call    0 returned 3
        3: 6614:        (void)newXSproto_portable("Stats::LikeR::glm", XS_Stats__LikeR_glm, file, ";@");
call    0 returned 3
        3: 6615:        (void)newXSproto_portable("Stats::LikeR::cor_test", XS_Stats__LikeR_cor_test, file, ";@");
call    0 returned 3
        3: 6616:        (void)newXSproto_portable("Stats::LikeR::shapiro_test", XS_Stats__LikeR_shapiro_test, file, "$");
call    0 returned 3
        3: 6617:        (void)newXSproto_portable("Stats::LikeR::min", XS_Stats__LikeR_min, file, "@");
call    0 returned 3
        3: 6618:        (void)newXSproto_portable("Stats::LikeR::max", XS_Stats__LikeR_max, file, "@");
call    0 returned 3
        3: 6619:        (void)newXSproto_portable("Stats::LikeR::runif", XS_Stats__LikeR_runif, file, ";@");
call    0 returned 3
        3: 6620:        (void)newXSproto_portable("Stats::LikeR::rbinom", XS_Stats__LikeR_rbinom, file, ";@");
call    0 returned 3
        3: 6621:        (void)newXSproto_portable("Stats::LikeR::hist", XS_Stats__LikeR_hist, file, "$;@");
call    0 returned 3
        3: 6622:        (void)newXSproto_portable("Stats::LikeR::quantile", XS_Stats__LikeR_quantile, file, ";@");
call    0 returned 3
        3: 6623:        (void)newXSproto_portable("Stats::LikeR::mean", XS_Stats__LikeR_mean, file, "@");
call    0 returned 3
        3: 6624:        (void)newXSproto_portable("Stats::LikeR::sd", XS_Stats__LikeR_sd, file, "@");
call    0 returned 3
        3: 6625:        (void)newXSproto_portable("Stats::LikeR::var", XS_Stats__LikeR_var, file, "@");
call    0 returned 3
        3: 6626:        (void)newXSproto_portable("Stats::LikeR::t_test", XS_Stats__LikeR_t_test, file, ";@");
call    0 returned 3
        3: 6627:        (void)newXSproto_portable("Stats::LikeR::p_adjust", XS_Stats__LikeR_p_adjust, file, "$;$");
call    0 returned 3
        3: 6628:        (void)newXSproto_portable("Stats::LikeR::median", XS_Stats__LikeR_median, file, "@");
call    0 returned 3
        3: 6629:        (void)newXSproto_portable("Stats::LikeR::cor", XS_Stats__LikeR_cor, file, "$;$$");
call    0 returned 3
        3: 6630:        (void)newXSproto_portable("Stats::LikeR::scale", XS_Stats__LikeR_scale, file, "@");
call    0 returned 3
        3: 6631:        (void)newXSproto_portable("Stats::LikeR::matrix", XS_Stats__LikeR_matrix, file, ";@");
call    0 returned 3
        3: 6632:        (void)newXSproto_portable("Stats::LikeR::lm", XS_Stats__LikeR_lm, file, ";@");
call    0 returned 3
        3: 6633:        (void)newXSproto_portable("Stats::LikeR::seq", XS_Stats__LikeR_seq, file, "$$;$");
call    0 returned 3
        3: 6634:        (void)newXSproto_portable("Stats::LikeR::rnorm", XS_Stats__LikeR_rnorm, file, ";@");
call    0 returned 3
        3: 6635:        (void)newXSproto_portable("Stats::LikeR::aov", XS_Stats__LikeR_aov, file, "$$");
call    0 returned 3
        3: 6636:        newXS_deffile("Stats::LikeR::fisher_test", XS_Stats__LikeR_fisher_test);
call    0 returned 3
        3: 6637:        newXS_deffile("Stats::LikeR::power_t_test", XS_Stats__LikeR_power_t_test);
call    0 returned 3
        3: 6638:        newXS_deffile("Stats::LikeR::kruskal_test", XS_Stats__LikeR_kruskal_test);
call    0 returned 3
        -: 6639:#if PERL_VERSION_LE(5, 21, 5)
        -: 6640:#  if PERL_VERSION_GE(5, 9, 0)
        -: 6641:    if (PL_unitcheckav)
        -: 6642:        call_list(PL_scopestack_ix, PL_unitcheckav);
        -: 6643:#  endif
        -: 6644:    XSRETURN_YES;
        -: 6645:#else
        3: 6646:    Perl_xs_boot_epilog(aTHX_ ax);
call    0 returned 3
        -: 6647:#endif
        3: 6648:}
        -: 6649:
        -: 6650:#ifdef __cplusplus
        -: 6651:}
        -: 6652:#endif
