        -:    0:Source:LikeR.c
        -:    0:Graph:./Stats-LikeR-0.01/LikeR.gcno
        -:    0:Data:./Stats-LikeR-0.01/LikeR.gcda
        -:    0:Runs:3
        -:    0:Source is newer than graph
        -:    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:
        -: 1274:// Bisection algorithm to find the inverse F-distribution (Quantile function)
        -: 1275:// Equivalent to R's qf(p, df1, df2)
        -: 1276:static double qf_bisection(double p, double df1, double df2) {
        -: 1277:	if (p <= 0.0) return 0.0;
        -: 1278:	if (p >= 1.0) return INFINITY;
        -: 1279:	double low = 0.0, high = 1.0;
        -: 1280:	// Find upper bound
        -: 1281:	while (pf(high, df1, df2) < p) {
        -: 1282:	  low = high;
        -: 1283:	  high *= 2.0;
        -: 1284:	  if (high > 1e100) break; /* Fallback limit */
        -: 1285:	}
        -: 1286:
        -: 1287:	// Bisect to find the root
        -: 1288:	for (unsigned short int i = 0; i < 150; i++) {
        -: 1289:		double mid = low + (high - low) / 2.0;
        -: 1290:		double p_mid = pf(mid, df1, df2);
        -: 1291:
        -: 1292:		if (p_mid < p) {
        -: 1293:			low = mid;
        -: 1294:		} else {
        -: 1295:			high = mid;
        -: 1296:		}
        -: 1297:		if (high - low < 1e-12) break;
        -: 1298:	}
        -: 1299:	return (low + high) / 2.0;
        -: 1300:}
        -: 1301:// --- XS SECTION ---
        -: 1302:#line 1303 "LikeR.c"
        -: 1303:#ifndef PERL_UNUSED_VAR
        -: 1304:#  define PERL_UNUSED_VAR(var) if (0) var = var
        -: 1305:#endif
        -: 1306:
        -: 1307:#ifndef dVAR
        -: 1308:#  define dVAR		dNOOP
        -: 1309:#endif
        -: 1310:
        -: 1311:
        -: 1312:/* This stuff is not part of the API! You have been warned. */
        -: 1313:#ifndef PERL_VERSION_DECIMAL
        -: 1314:#  define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)
        -: 1315:#endif
        -: 1316:#ifndef PERL_DECIMAL_VERSION
        -: 1317:#  define PERL_DECIMAL_VERSION \
        -: 1318:	  PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)
        -: 1319:#endif
        -: 1320:#ifndef PERL_VERSION_GE
        -: 1321:#  define PERL_VERSION_GE(r,v,s) \
        -: 1322:	  (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))
        -: 1323:#endif
        -: 1324:#ifndef PERL_VERSION_LE
        -: 1325:#  define PERL_VERSION_LE(r,v,s) \
        -: 1326:	  (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s))
        -: 1327:#endif
        -: 1328:
        -: 1329:/* XS_INTERNAL is the explicit static-linkage variant of the default
        -: 1330: * XS macro.
        -: 1331: *
        -: 1332: * XS_EXTERNAL is the same as XS_INTERNAL except it does not include
        -: 1333: * "STATIC", ie. it exports XSUB symbols. You probably don't want that
        -: 1334: * for anything but the BOOT XSUB.
        -: 1335: *
        -: 1336: * See XSUB.h in core!
        -: 1337: */
        -: 1338:
        -: 1339:
        -: 1340:/* TODO: This might be compatible further back than 5.10.0. */
        -: 1341:#if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1)
        -: 1342:#  undef XS_EXTERNAL
        -: 1343:#  undef XS_INTERNAL
        -: 1344:#  if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING)
        -: 1345:#    define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name)
        -: 1346:#    define XS_INTERNAL(name) STATIC XSPROTO(name)
        -: 1347:#  endif
        -: 1348:#  if defined(__SYMBIAN32__)
        -: 1349:#    define XS_EXTERNAL(name) EXPORT_C XSPROTO(name)
        -: 1350:#    define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name)
        -: 1351:#  endif
        -: 1352:#  ifndef XS_EXTERNAL
        -: 1353:#    if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus)
        -: 1354:#      define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__)
        -: 1355:#      define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__)
        -: 1356:#    else
        -: 1357:#      ifdef __cplusplus
        -: 1358:#        define XS_EXTERNAL(name) extern "C" XSPROTO(name)
        -: 1359:#        define XS_INTERNAL(name) static XSPROTO(name)
        -: 1360:#      else
        -: 1361:#        define XS_EXTERNAL(name) XSPROTO(name)
        -: 1362:#        define XS_INTERNAL(name) STATIC XSPROTO(name)
        -: 1363:#      endif
        -: 1364:#    endif
        -: 1365:#  endif
        -: 1366:#endif
        -: 1367:
        -: 1368:/* perl >= 5.10.0 && perl <= 5.15.1 */
        -: 1369:
        -: 1370:
        -: 1371:/* The XS_EXTERNAL macro is used for functions that must not be static
        -: 1372: * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL
        -: 1373: * macro defined, the best we can do is assume XS is the same.
        -: 1374: * Dito for XS_INTERNAL.
        -: 1375: */
        -: 1376:#ifndef XS_EXTERNAL
        -: 1377:#  define XS_EXTERNAL(name) XS(name)
        -: 1378:#endif
        -: 1379:#ifndef XS_INTERNAL
        -: 1380:#  define XS_INTERNAL(name) XS(name)
        -: 1381:#endif
        -: 1382:
        -: 1383:/* Now, finally, after all this mess, we want an ExtUtils::ParseXS
        -: 1384: * internal macro that we're free to redefine for varying linkage due
        -: 1385: * to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use
        -: 1386: * XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to!
        -: 1387: */
        -: 1388:
        -: 1389:#undef XS_EUPXS
        -: 1390:#if defined(PERL_EUPXS_ALWAYS_EXPORT)
        -: 1391:#  define XS_EUPXS(name) XS_EXTERNAL(name)
        -: 1392:#else
        -: 1393:   /* default to internal */
        -: 1394:#  define XS_EUPXS(name) XS_INTERNAL(name)
        -: 1395:#endif
        -: 1396:
        -: 1397:#ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE
        -: 1398:#define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params)
        -: 1399:
        -: 1400:/* prototype to pass -Wmissing-prototypes */
        -: 1401:STATIC void
        -: 1402:S_croak_xs_usage(const CV *const cv, const char *const params);
        -: 1403:
        -: 1404:STATIC void
        -: 1405:S_croak_xs_usage(const CV *const cv, const char *const params)
        -: 1406:{
        -: 1407:    const GV *const gv = CvGV(cv);
        -: 1408:
        -: 1409:    PERL_ARGS_ASSERT_CROAK_XS_USAGE;
        -: 1410:
        -: 1411:    if (gv) {
        -: 1412:        const char *const gvname = GvNAME(gv);
        -: 1413:        const HV *const stash = GvSTASH(gv);
        -: 1414:        const char *const hvname = stash ? HvNAME(stash) : NULL;
        -: 1415:
        -: 1416:        if (hvname)
        -: 1417:	    Perl_croak_nocontext("Usage: %s::%s(%s)", hvname, gvname, params);
        -: 1418:        else
        -: 1419:	    Perl_croak_nocontext("Usage: %s(%s)", gvname, params);
        -: 1420:    } else {
        -: 1421:        /* Pants. I don't think that it should be possible to get here. */
        -: 1422:	Perl_croak_nocontext("Usage: CODE(0x%" UVxf ")(%s)", PTR2UV(cv), params);
        -: 1423:    }
        -: 1424:}
        -: 1425:#undef  PERL_ARGS_ASSERT_CROAK_XS_USAGE
        -: 1426:
        -: 1427:#define croak_xs_usage        S_croak_xs_usage
function XS_Stats__LikeR_ks_test called 12 returned 100% blocks executed 77%
       12: 1428:
        -: 1429:#endif
       12: 1430:
       12: 1430-block  0
call    0 returned 12
branch  1 taken 12 (fallthrough)
branch  2 taken 0
        -: 1431:/* NOTE: the prototype of newXSproto() is different in versions of perls,
        -: 1432: * so we define a portable version of newXSproto()
        -: 1433: */
        -: 1434:#ifdef newXS_flags
        -: 1435:#define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0)
        -: 1436:#else
        -: 1437:#define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv)
        -: 1438:#endif /* !defined(newXS_flags) */
        -: 1439:
        -: 1440:#if PERL_VERSION_LE(5, 21, 5)
        -: 1441:#  define newXS_deffile(a,b) Perl_newXS(aTHX_ a,b,file)
        -: 1442:#else
        -: 1443:#  define newXS_deffile(a,b) Perl_newXS_deffile(aTHX_ a,b)
        -: 1444:#endif
        -: 1445:
        -: 1446:/* simple backcompat versions of the TARGx() macros with no optimisation */
        -: 1447:#ifndef TARGi
        -: 1448:#  define TARGi(iv, do_taint) sv_setiv_mg(TARG, iv)
        -: 1449:#  define TARGu(uv, do_taint) sv_setuv_mg(TARG, uv)
        -: 1450:#  define TARGn(nv, do_taint) sv_setnv_mg(TARG, nv)
        -: 1451:#endif
        -: 1452:
        -: 1453:#line 1454 "LikeR.c"
        -: 1454:
        -: 1455:XS_EUPXS(XS_Stats__LikeR_ks_test); /* prototype to pass -Wmissing-prototypes */
        -: 1456:XS_EUPXS(XS_Stats__LikeR_ks_test)
        -: 1457:{
        -: 1458:    dVAR; dXSARGS;
        -: 1459:    PERL_UNUSED_VAR(cv); /* -W */
        -: 1460:    PERL_UNUSED_VAR(items); /* -W */
        -: 1461:    {
        -: 1462:	SV *	RETVAL;
        -: 1463:#line 1297 "LikeR.xs"
        -: 1464:{
        -: 1465:	SV *restrict x_sv = NULL, *restrict y_sv = NULL;
        -: 1466:	short int exact = -1;
        -: 1467:	const char *restrict alternative = "two.sided";
        -: 1468:	int arg_idx = 0;
        -: 1469:
        -: 1470:	// Shift arrays if provided positionally
        -: 1471:	if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 1472:		x_sv = ST(arg_idx);
        -: 1473:		arg_idx++;
        -: 1474:	}
        -: 1475:	// Check if second argument is an array (2-sample) or a string representing a CDF (1-sample)
        -: 1476:	if (arg_idx < items) {
        -: 1477:		if (SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 1478:			y_sv = ST(arg_idx);
        -: 1479:			arg_idx++;
        -: 1480:		} else if (SvPOK(ST(arg_idx))) {
        -: 1481:			y_sv = ST(arg_idx); // Save string (e.g., "pnorm") for 1-sample test logic
        -: 1482:			arg_idx++;
        -: 1483:		}
        -: 1484:	}
        -: 1485:
        -: 1486:	// Parse named arguments
        -: 1487:	for (; arg_idx < items; arg_idx += 2) {
        -: 1488:	  const char *restrict key = SvPV_nolen(ST(arg_idx));
        -: 1489:	  SV *restrict val = ST(arg_idx + 1);
        -: 1490:	  if      (strEQ(key, "x"))           x_sv = val;
        -: 1491:	  else if (strEQ(key, "y"))           y_sv = val;
        -: 1492:	  else if (strEQ(key, "exact"))       {
        -: 1493:		   if (!SvOK(val)) exact = -1;
        -: 1494:		   else exact = SvTRUE(val) ? 1 : 0;
        -: 1495:	  }
        -: 1496:	  else if (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 1497:	  else croak("ks_test: unknown argument '%s'", key);
        -: 1498:	}
        -: 1499:
        -: 1500:	if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV) {
        -: 1501:	  croak("ks_test: 'x' is a required argument and must be an ARRAY reference");
        -: 1502:	}
        -: 1503:
        -: 1504:	bool is_two_sided = strEQ(alternative, "two.sided") ? 1 : 0;
        -: 1505:	bool is_greater   = strEQ(alternative, "greater") ? 1 : 0;
        -: 1506:	bool is_less      = strEQ(alternative, "less") ? 1 : 0;
        -: 1507:
        -: 1508:	if (!is_two_sided && !is_greater && !is_less) {
        -: 1509:	  croak("ks_test: alternative must be 'two.sided', 'less', or 'greater'");
        -: 1510:	}
        -: 1511:
        -: 1512:	AV *restrict x_av = (AV*)SvRV(x_sv);
        -: 1513:	size_t nx = av_len(x_av) + 1;
        -: 1514:	if (nx == 0) croak("Not enough 'x' observations");
        -: 1515:
        -: 1516:	// Extract 'x' array to C-array
        -: 1517:	double *restrict x_data = (double *)safemalloc(nx * sizeof(double));
        -: 1518:	size_t valid_nx = 0;
        -: 1519:	for (size_t i = 0; i < nx; i++) {
        -: 1520:	  SV**restrict el = av_fetch(x_av, i, 0);
        -: 1521:	  if (el && SvOK(*el) && looks_like_number(*el)) {
        -: 1522:		   x_data[valid_nx++] = SvNV(*el);
        -: 1523:	  }
        -: 1524:	}
        -: 1525:
        -: 1526:	double statistic = 0.0, p_value = 0.0;
        -: 1527:	const char *restrict method_desc = "";
        -: 1528:
        -: 1529:	// --- TWO SAMPLE ---
        -: 1530:	if (y_sv && SvROK(y_sv) && SvTYPE(SvRV(y_sv)) == SVt_PVAV) {
        -: 1531:	  AV *restrict y_av = (AV*)SvRV(y_sv);
        -: 1532:	  size_t ny = av_len(y_av) + 1;
        -: 1533:
        -: 1534:	  double *restrict y_data = (double *)safemalloc(ny * sizeof(double));
        -: 1535:	  size_t valid_ny = 0;
        -: 1536:	  for (size_t i = 0; i < ny; i++) {
        -: 1537:		   SV**restrict el = av_fetch(y_av, i, 0);
        -: 1538:		   if (el && SvOK(*el) && looks_like_number(*el)) {
        -: 1539:		       y_data[valid_ny++] = SvNV(*el);
        -: 1540:		   }
        -: 1541:	  }
        -: 1542:
        -: 1543:	  if (valid_nx < 1 || valid_ny < 1) {
        -: 1544:		   Safefree(x_data); Safefree(y_data);
        -: 1545:		   croak("Not enough non-missing observations for KS test");
        -: 1546:	  }
        -: 1547:
        -: 1548:	  double d, d_plus, d_minus;
        -: 1549:	  calc_2sample_stats(x_data, valid_nx, y_data, valid_ny, &d, &d_plus, &d_minus);
        -: 1550:
        -: 1551:	  // Map alternative to the correct statistic
        -: 1552:	  if (is_greater) statistic = d_plus;
        -: 1553:	  else if (is_less) statistic = d_minus;
        -: 1554:	  else statistic = d;
        -: 1555:
        -: 1556:	  // Determine if exact or asymptotic
        -: 1557:	  bool use_exact = false;
        -: 1558:	  if (exact == 1) use_exact = true;
        -: 1559:	  else if (exact == 0) use_exact = false;
        -: 1560:	  else use_exact = (valid_nx * valid_ny < 10000); 
        -: 1561:
        -: 1562:	  // Check for ties in combined set
        -: 1563:	  size_t total_n = valid_nx + valid_ny;
        -: 1564:	  double *restrict comb = (double *)safemalloc(total_n * sizeof(double));
        -: 1565:	  for(size_t i=0; i<valid_nx; i++) comb[i] = x_data[i];
        -: 1566:	  for(size_t i=0; i<valid_ny; i++) comb[valid_nx+i] = y_data[i];
        -: 1567:	  qsort(comb, total_n, sizeof(double), compare_doubles);
        -: 1568:
        -: 1569:	  bool has_ties = false;
        -: 1570:	  for(size_t i = 1; i < total_n; i++) {
        -: 1571:		   if(comb[i] == comb[i-1]) { has_ties = true; break; }
        -: 1572:	  }
        -: 1573:	  Safefree(comb);
        -: 1574:	  if (use_exact && has_ties) {
        -: 1575:		   warn("cannot compute exact p-value with ties; falling back to asymptotic");
        -: 1576:		   use_exact = false;
        -: 1577:	  }
        -: 1578:	  if (use_exact) {
        -: 1579:		   method_desc = "Two-sample Kolmogorov-Smirnov exact test";
        -: 1580:		   double q = (0.5 + floor(statistic * valid_nx * valid_ny - 1e-7)) / ((double)valid_nx * valid_ny);
        -: 1581:		   p_value = psmirnov_exact_uniq_upper(q, valid_nx, valid_ny, is_two_sided);
        -: 1582:	  } else {
        -: 1583:		   method_desc = "Two-sample Kolmogorov-Smirnov test (asymptotic)";
        -: 1584:		   double z = statistic * sqrt((double)(valid_nx * valid_ny) / (valid_nx + valid_ny));
        -: 1585:		   if (is_two_sided) {
        -: 1586:		       p_value = K2l(z, 0, 1e-9); 
        -: 1587:		   } else {
        -: 1588:		       p_value = exp(-2.0 * z * z); // One-sided limit distribution
        -: 1589:		   }
        -: 1590:	  }
        -: 1591:	  Safefree(y_data);
        -: 1592:	} 
        -: 1593:	// --- ONE SAMPLE (e.g. against pnorm) ---
        -: 1594:	else if (y_sv && SvPOK(y_sv)) {
        -: 1595:	  const char *restrict dist = SvPV_nolen(y_sv);
        -: 1596:	  if (strEQ(dist, "pnorm")) {
        -: 1597:		   qsort(x_data, valid_nx, sizeof(double), compare_doubles);
        -: 1598:		   double max_d = 0.0, max_d_plus = 0.0, max_d_minus = 0.0;
        -: 1599:		   for(size_t i = 0; i < valid_nx; i++) {
        -: 1600:		       double cdf_obs_low  = (double)i / valid_nx;
        -: 1601:		       double cdf_obs_high = (double)(i + 1) / valid_nx;
        -: 1602:		       double cdf_theor    = approx_pnorm(x_data[i]);
        -: 1603:
        -: 1604:		       double diff1 = cdf_obs_low - cdf_theor;
        -: 1605:		       double diff2 = cdf_obs_high - cdf_theor;
        -: 1606:
        -: 1607:		       if (diff1 > max_d_plus) max_d_plus = diff1;
        -: 1608:		       if (diff2 > max_d_plus) max_d_plus = diff2;
        -: 1609:		       if (-diff1 > max_d_minus) max_d_minus = -diff1;
        -: 1610:		       if (-diff2 > max_d_minus) max_d_minus = -diff2;
        -: 1611:
        -: 1612:		       if (fabs(diff1) > max_d) max_d = fabs(diff1);
        -: 1613:		       if (fabs(diff2) > max_d) max_d = fabs(diff2);
        -: 1614:		   }
        -: 1615:		   if (is_greater) statistic = max_d_plus;
        -: 1616:		   else if (is_less) statistic = max_d_minus;
        -: 1617:		   else statistic = max_d;
        -: 1618:		   bool use_exact = (exact == -1) ? (valid_nx < 100) : (exact == 1);
        -: 1619:		   if (use_exact) {
        -: 1620:		       method_desc = "One-sample Kolmogorov-Smirnov exact test";
        -: 1621:		       if (is_two_sided) {
        -: 1622:		           p_value = 1.0 - K2x(valid_nx, statistic);
        -: 1623:		       } else {
        -: 1624:		           warn("exact 1-sample 1-sided KS test not implemented; using asymptotic");
       12: 1625:		           double z = statistic * sqrt((double)valid_nx);
call    0 returned 12
       12: 1626:		           p_value = exp(-2.0 * z * z);
        -: 1627:		       }
       12: 1628:		   } else {
        -: 1629:		       method_desc = "One-sample Kolmogorov-Smirnov test (asymptotic)";
        -: 1630:		       double z = statistic * sqrt((double)valid_nx);
        -: 1631:		       if (is_two_sided) p_value = K2l(z, 0, 1e-6); 
        -: 1632:		       else p_value = exp(-2.0 * z * z);
function XS_Stats__LikeR_wilcox_test called 30 returned 80% blocks executed 72%
       30: 1633:		   }
        -: 1634:	  } else {
       30: 1635:		    Safefree(x_data);
       30: 1635-block  0
call    0 returned 30
branch  1 taken 30 (fallthrough)
branch  2 taken 0
        -: 1636:		    croak("ks_test: Unsupported 1-sample distribution '%s'. Use arrays for 2-sample.", dist);
        -: 1637:	  }
        -: 1638:	} else {
        -: 1639:	  Safefree(x_data);
        -: 1640:	  croak("ks_test: Invalid arguments for 'y'.");
        -: 1641:	}
        -: 1642:	Safefree(x_data);
        -: 1643:	if (p_value > 1.0) p_value = 1.0;
        -: 1644:	if (p_value < 0.0) p_value = 0.0;
        -: 1645:	HV *restrict res = newHV();
        -: 1646:	hv_stores(res, "statistic", newSVnv(statistic));
        -: 1647:	hv_stores(res, "p_value", newSVnv(p_value));
        -: 1648:	hv_stores(res, "method", newSVpv(method_desc, 0));
        -: 1649:	hv_stores(res, "alternative", newSVpv(alternative, 0));
        -: 1650:	RETVAL = newRV_noinc((SV*)res);
        -: 1651:}
        -: 1652:#line 1653 "LikeR.c"
        -: 1653:	RETVAL = sv_2mortal(RETVAL);
        -: 1654:	ST(0) = RETVAL;
        -: 1655:    }
        -: 1656:    XSRETURN(1);
        -: 1657:}
        -: 1658:
        -: 1659:
        -: 1660:XS_EUPXS(XS_Stats__LikeR_wilcox_test); /* prototype to pass -Wmissing-prototypes */
        -: 1661:XS_EUPXS(XS_Stats__LikeR_wilcox_test)
        -: 1662:{
        -: 1663:    dVAR; dXSARGS;
        -: 1664:    PERL_UNUSED_VAR(cv); /* -W */
        -: 1665:    PERL_UNUSED_VAR(items); /* -W */
        -: 1666:    {
        -: 1667:	SV *	RETVAL;
        -: 1668:#line 1490 "LikeR.xs"
        -: 1669:{
        -: 1670:	SV *restrict x_sv = NULL, *restrict y_sv = NULL;
        -: 1671:	bool paired = false, correct = true;
        -: 1672:	double mu = 0.0;
        -: 1673:	short int exact = -1;
        -: 1674:	const char *restrict alternative = "two.sided";
        -: 1675:	int arg_idx = 0;
        -: 1676:	// 1. Shift first positional argument as 'x' if it's an array reference
        -: 1677:	if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 1678:		x_sv = ST(arg_idx);
        -: 1679:		arg_idx++;
        -: 1680:	}
        -: 1681:	// 2. Shift second positional argument as 'y' if it's an array reference
        -: 1682:	if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 1683:		y_sv = ST(arg_idx);
        -: 1684:		arg_idx++;
        -: 1685:	}
        -: 1686:	// Ensure the remaining arguments form complete key-value pairs
        -: 1687:	if ((items - arg_idx) % 2 != 0) {
        -: 1688:		croak("Usage: wilcox_test(\\@x, [\\@y], key => value, ...)");
        -: 1689:	}
        -: 1690:	// --- Parse named arguments from the remaining flat stack ---
        -: 1691:	for (; arg_idx < items; arg_idx += 2) {
        -: 1692:		const char *restrict key = SvPV_nolen(ST(arg_idx));
        -: 1693:		SV *restrict val = ST(arg_idx + 1);
        -: 1694:		if      (strEQ(key, "x"))           x_sv = val;
        -: 1695:		else if (strEQ(key, "y"))           y_sv = val;
        -: 1696:		else if (strEQ(key, "paired"))      paired = SvTRUE(val);
        -: 1697:		else if (strEQ(key, "correct"))     correct = SvTRUE(val);
        -: 1698:		else if (strEQ(key, "mu"))          mu = SvNV(val);
        -: 1699:		else if (strEQ(key, "exact"))       {
        -: 1700:			if (!SvOK(val)) exact = -1;
        -: 1701:			else exact = SvTRUE(val) ? 1 : 0;
        -: 1702:		}
        -: 1703:		else if (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 1704:		else croak("wilcox_test: unknown argument '%s'", key);
        -: 1705:	}
        -: 1706:	// --- Validate required / types ---
        -: 1707:	if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 1708:		croak("wilcox_test: 'x' is a required argument and must be an ARRAY reference");
        -: 1709:	AV *restrict x_av = (AV*)SvRV(x_sv);
        -: 1710:	size_t nx = av_len(x_av) + 1;
        -: 1711:	if (nx == 0) croak("Not enough 'x' observations");
        -: 1712:
        -: 1713:	AV *restrict y_av = NULL;
        -: 1714:	size_t ny = 0;
        -: 1715:	if (y_sv && SvROK(y_sv) && SvTYPE(SvRV(y_sv)) == SVt_PVAV) {
        -: 1716:		y_av = (AV*)SvRV(y_sv);
        -: 1717:		ny = av_len(y_av) + 1;
        -: 1718:	}
        -: 1719:	double p_value = 0.0, statistic = 0.0;
        -: 1720:	const char *restrict method_desc = "";
        -: 1721:	bool use_exact = false;
        -: 1722:	// --- TWO SAMPLE (Mann-Whitney) ---
        -: 1723:	if (ny > 0 && !paired) {
        -: 1724:		RankInfo *restrict ri = (RankInfo *)safemalloc((nx + ny) * sizeof(RankInfo));
        -: 1725:		size_t valid_nx = 0, valid_ny = 0;
        -: 1726:		for (size_t i = 0; i < nx; i++) {
        -: 1727:			SV**restrict el = av_fetch(x_av, i, 0);
        -: 1728:			if (el && SvOK(*el) && looks_like_number(*el)) {
        -: 1729:				ri[valid_nx].val = SvNV(*el) - mu; // R subtracts mu from x
        -: 1730:				ri[valid_nx].idx = 1;
        -: 1731:				valid_nx++;
        -: 1732:			}
        -: 1733:		}
        -: 1734:		for (size_t i = 0; i < ny; i++) {
        -: 1735:			SV**restrict el = av_fetch(y_av, i, 0);
        -: 1736:			if (el && SvOK(*el) && looks_like_number(*el)) {
        -: 1737:				ri[valid_nx + valid_ny].val = SvNV(*el);
        -: 1738:				ri[valid_nx + valid_ny].idx = 2;
        -: 1739:				valid_ny++;
        -: 1740:			}
        -: 1741:		}
        -: 1742:		if (valid_nx == 0) { Safefree(ri); croak("not enough (non-missing) 'x' observations"); }
        -: 1743:		if (valid_ny == 0) { Safefree(ri); croak("not enough 'y' observations"); }
        -: 1744:		size_t total_n = valid_nx + valid_ny;
        -: 1745:		bool has_ties = 0;
        -: 1746:		double tie_adj = rank_and_count_ties(ri, total_n, &has_ties);
        -: 1747:		double w_rank_sum = 0.0;
        -: 1748:		for (size_t i = 0; i < total_n; i++) if (ri[i].idx == 1) w_rank_sum += ri[i].rank;
        -: 1749:		statistic = w_rank_sum - (double)valid_nx * (valid_nx + 1.0) / 2.0;
        -: 1750:
        -: 1751:		if (exact == 1) use_exact = true;
        -: 1752:		else if (exact == 0) use_exact = false;
        -: 1753:		else use_exact = (valid_nx < 50 && valid_ny < 50 && !has_ties);
        -: 1754:
        -: 1755:		if (use_exact && has_ties) {
        -: 1756:			warn("cannot compute exact p-value with ties; falling back to approximation");
        -: 1757:			use_exact = false;
        -: 1758:		}
        -: 1759:		if (use_exact) {
        -: 1760:			method_desc = "Wilcoxon rank sum exact test";
        -: 1761:			double p_less = exact_pwilcox(statistic, valid_nx, valid_ny);
        -: 1762:			double p_greater = 1.0 - exact_pwilcox(statistic - 1.0, valid_nx, valid_ny);
        -: 1763:
        -: 1764:			if (strcmp(alternative, "less") == 0) p_value = p_less;
        -: 1765:			else if (strcmp(alternative, "greater") == 0) p_value = p_greater;
        -: 1766:			else {
        -: 1767:				double p = (p_less < p_greater) ? p_less : p_greater;
        -: 1768:				p_value = 2.0 * p;
        -: 1769:			}
        -: 1770:		} else {
        -: 1771:			method_desc = correct ? "Wilcoxon rank sum test with continuity correction" : "Wilcoxon rank sum test";
        -: 1772:			double exp = (double)valid_nx * valid_ny / 2.0;
        -: 1773:			double var = ((double)valid_nx * valid_ny / 12.0) * ((total_n + 1.0) - tie_adj / (total_n * (total_n - 1.0)));
        -: 1774:			double z = statistic - exp;
        -: 1775:
        -: 1776:			double CORRECTION = 0.0;
        -: 1777:			if (correct) {
        -: 1778:				if (strcmp(alternative, "two.sided") == 0) CORRECTION = (z > 0 ? 0.5 : -0.5);
        -: 1779:				else if (strcmp(alternative, "greater") == 0) CORRECTION = 0.5;
        -: 1780:				else if (strcmp(alternative, "less") == 0) CORRECTION = -0.5;
        -: 1781:			}
        -: 1782:			z = (z - CORRECTION) / sqrt(var);
        -: 1783:
        -: 1784:			if (strcmp(alternative, "less") == 0) p_value = approx_pnorm(z);
        -: 1785:			else if (strcmp(alternative, "greater") == 0) p_value = 1.0 - approx_pnorm(z);
        -: 1786:			else p_value = 2.0 * approx_pnorm(-fabs(z));
        -: 1787:		}
        -: 1788:		Safefree(ri);
        -: 1789:	} else { // --- ONE SAMPLE / PAIRED ---
        -: 1790:		if (paired && (!y_av || nx != ny)) croak("'x' and 'y' must have the same length for paired test");
        -: 1791:		double *restrict diffs = (double *)safemalloc(nx * sizeof(double));
        -: 1792:		size_t n_nz = 0;
        -: 1793:		bool has_zeroes = false;
        -: 1794:		for (size_t i = 0; i < nx; i++) {
        -: 1795:			SV**restrict x_el = av_fetch(x_av, i, 0);
        -: 1796:			if (!x_el || !SvOK(*x_el) || !looks_like_number(*x_el)) continue;
        -: 1797:			double dx = SvNV(*x_el);
        -: 1798:
        -: 1799:			if (paired) {
        -: 1800:				SV**restrict y_el = av_fetch(y_av, i, 0);
        -: 1801:				if (!y_el || !SvOK(*y_el) || !looks_like_number(*y_el)) continue;
        -: 1802:				double dy = SvNV(*y_el);
        -: 1803:				double d = dx - dy - mu;
        -: 1804:				if (d == 0.0) has_zeroes = true; // Drop exact zeroes
        -: 1805:				else diffs[n_nz++] = d;
        -: 1806:			} else {
        -: 1807:				double d = dx - mu;
        -: 1808:				if (d == 0.0) has_zeroes = true;
        -: 1809:				else diffs[n_nz++] = d;
        -: 1810:			}
        -: 1811:		}
        -: 1812:		if (n_nz == 0) {
        -: 1813:			Safefree(diffs);
        -: 1814:			croak("not enough (non-missing) observations");
        -: 1815:		}
        -: 1816:		RankInfo *ri = (RankInfo *)safemalloc(n_nz * sizeof(RankInfo));
        -: 1817:		for (size_t i = 0; i < n_nz; i++) { 
        -: 1818:			ri[i].val = fabs(diffs[i]); 
        -: 1819:			ri[i].idx = (diffs[i] > 0);
        -: 1820:		}
        -: 1821:		bool has_ties = 0;
        -: 1822:		double tie_adj = rank_and_count_ties(ri, n_nz, &has_ties);
        -: 1823:		statistic = 0.0;
        -: 1824:		for (size_t i = 0; i < n_nz; i++) {
        -: 1825:			if (ri[i].idx) statistic += ri[i].rank;
        -: 1826:		}
        -: 1827:		if (exact == 1) use_exact = true;
        -: 1828:		else if (exact == 0) use_exact = false;
        -: 1829:		else use_exact = (n_nz < 50 && !has_ties);
        -: 1830:		if (use_exact && has_ties) {
        -: 1831:			warn("cannot compute exact p-value with ties; falling back to approximation");
        -: 1832:			use_exact = false;
        -: 1833:		}
        -: 1834:		if (use_exact && has_zeroes) {
        -: 1835:			warn("cannot compute exact p-value with zeroes; falling back to approximation");
        -: 1836:			use_exact = false;
        -: 1837:		}
        -: 1838:		if (use_exact) {
        -: 1839:			method_desc = paired ? "Wilcoxon exact signed rank test" : "Wilcoxon exact signed rank test";
        -: 1840:			double p_less = exact_psignrank(statistic, n_nz);
        -: 1841:			double p_greater = 1.0 - exact_psignrank(statistic - 1.0, n_nz);
        -: 1842:
        -: 1843:			if (strcmp(alternative, "less") == 0) p_value = p_less;
        -: 1844:			else if (strcmp(alternative, "greater") == 0) p_value = p_greater;
        -: 1845:			else {
        -: 1846:				double p = (p_less < p_greater) ? p_less : p_greater;
        -: 1847:				p_value = 2.0 * p;
        -: 1848:			}
       24: 1849:		} else {
call    0 returned 24
       24: 1850:			method_desc = correct ? "Wilcoxon signed rank test with continuity correction" : "Wilcoxon signed rank test";
        -: 1851:			double exp = (double)n_nz * (n_nz + 1.0) / 4.0;
       24: 1852:			double var = (n_nz * (n_nz + 1.0) * (2.0 * n_nz + 1.0) / 24.0) - (tie_adj / 48.0);
        -: 1853:			double z = statistic - exp;
        -: 1854:			double CORRECTION = 0.0;
        -: 1855:			if (correct) {
        -: 1856:				if (strcmp(alternative, "two.sided") == 0) CORRECTION = (z > 0 ? 0.5 : -0.5);
function XS_Stats__LikeR__chisq_c called 9 returned 100% blocks executed 94%
        9: 1857:				else if (strcmp(alternative, "greater") == 0) CORRECTION = 0.5;
        -: 1858:				else if (strcmp(alternative, "less") == 0) CORRECTION = -0.5;
        9: 1859:			}
        9: 1859-block  0
call    0 returned 9
        9: 1860:			z = (z - CORRECTION) / sqrt(var);
branch  0 taken 0 (fallthrough)
branch  1 taken 9
    #####: 1861:
    %%%%%: 1861-block  0
call    0 never executed
        -: 1862:			if (strcmp(alternative, "less") == 0) p_value = approx_pnorm(z);
        9: 1863:			else if (strcmp(alternative, "greater") == 0) p_value = 1.0 - approx_pnorm(z);
        9: 1863-block  0
branch  0 taken 0 (fallthrough)
branch  1 taken 9
        -: 1864:			else p_value = 2.0 * approx_pnorm(-fabs(z));
        -: 1865:		}
        -: 1866:		Safefree(ri); Safefree(diffs);
        -: 1867:	}
        -: 1868:	if (p_value > 1.0) p_value = 1.0;
        -: 1869:	HV *restrict res = newHV();
        -: 1870:	hv_stores(res, "statistic", newSVnv(statistic));
        -: 1871:	hv_stores(res, "p_value", newSVnv(p_value));
        -: 1872:	hv_stores(res, "method", newSVpv(method_desc, 0));
        -: 1873:	hv_stores(res, "alternative", newSVpv(alternative, 0));
        -: 1874:	RETVAL = newRV_noinc((SV*)res);
        -: 1875:}
        -: 1876:#line 1877 "LikeR.c"
        -: 1877:	RETVAL = sv_2mortal(RETVAL);
        -: 1878:	ST(0) = RETVAL;
        -: 1879:    }
        -: 1880:    XSRETURN(1);
        -: 1881:}
        -: 1882:
        -: 1883:
        -: 1884:XS_EUPXS(XS_Stats__LikeR__chisq_c); /* prototype to pass -Wmissing-prototypes */
        -: 1885:XS_EUPXS(XS_Stats__LikeR__chisq_c)
        -: 1886:{
        -: 1887:    dVAR; dXSARGS;
        -: 1888:    if (items != 1)
        -: 1889:       croak_xs_usage(cv,  "data_ref");
        -: 1890:    {
        -: 1891:	SV*	data_ref = ST(0)
        -: 1892:;
        -: 1893:	SV *	RETVAL;
        -: 1894:#line 1703 "LikeR.xs"
        -: 1895:{
        -: 1896:	AV*restrict obs_av = (AV*)SvRV(data_ref);
        -: 1897:	int r = av_top_index(obs_av) + 1, c = 0;
        -: 1898:	bool is_2d = 0;
        -: 1899:	SV**restrict first_elem = av_fetch(obs_av, 0, 0);
        -: 1900:	if (first_elem && SvROK(*first_elem) && SvTYPE(SvRV(*first_elem)) == SVt_PVAV) {
        -: 1901:		is_2d = 1;
        -: 1902:		AV*restrict first_row = (AV*)SvRV(*first_elem);
        -: 1903:		c = av_top_index(first_row) + 1;
        -: 1904:	} else {
        -: 1905:		c = r;
        -: 1906:		r = 1;
        -: 1907:	}
        -: 1908:
        -: 1909:	double stat = 0.0, grand_total = 0.0;
        -: 1910:	int df = 0;
        -: 1911:	int yates = (is_2d && r == 2 && c == 2) ? 1 : 0;
        -: 1912:
        -: 1913:	AV*restrict expected_av = newAV();
        -: 1914:	if (is_2d) {
        -: 1915:		double *restrict row_sum = (double*)safemalloc(r * sizeof(double));
        -: 1916:		double *restrict col_sum = (double*)safemalloc(c * sizeof(double));
        -: 1917:		for(unsigned int i=0; i<r; i++) row_sum[i] = 0.0;
        -: 1918:		for(unsigned int j=0; j<c; j++) col_sum[j] = 0.0;
        -: 1919:		for (unsigned int i = 0; i < r; i++) {
        -: 1920:			SV**restrict row_sv = av_fetch(obs_av, i, 0);
        -: 1921:			AV*restrict row = (AV*)SvRV(*row_sv);
        -: 1922:			for (unsigned int j = 0; j < c; j++) {
        -: 1923:				 SV**restrict val_sv = av_fetch(row, j, 0);
        -: 1924:				 double val = SvNV(*val_sv);
        -: 1925:				 row_sum[i] += val;
        -: 1926:				 col_sum[j] += val;
        -: 1927:				 grand_total += val;
        -: 1928:			}
        -: 1929:		}
        -: 1930:		for (unsigned int i = 0; i < r; i++) {
        -: 1931:			AV*restrict exp_row = newAV();
        -: 1932:			SV**restrict row_sv = av_fetch(obs_av, i, 0);
        -: 1933:			AV*restrict row = (AV*)SvRV(*row_sv);
        -: 1934:			for (unsigned int j = 0; j < c; j++) {
        -: 1935:				double E = (row_sum[i] * col_sum[j]) / grand_total;
        -: 1936:				SV**restrict val_sv = av_fetch(row, j, 0);
        -: 1937:				double O = SvNV(*val_sv);
        -: 1938:				av_push(exp_row, newSVnv(E));
        -: 1939:				if (yates) {
        -: 1940:				  // Exact R logic: min(0.5, abs(O - E))
        -: 1941:				  double abs_diff = fabs(O - E);
        -: 1942:				  double y_corr = (abs_diff > 0.5) ? 0.5 : abs_diff;
        -: 1943:				  double diff = abs_diff - y_corr;
        -: 1944:				  stat += (diff * diff) / E;
        -: 1945:				} else {
        -: 1946:				  stat += ((O - E) * (O - E)) / E;
        -: 1947:				}
        -: 1948:			}
        -: 1949:			av_push(expected_av, newRV_noinc((SV*)exp_row));
        -: 1950:		}
        -: 1951:		safefree(row_sum); safefree(col_sum);
        -: 1952:		df = (r - 1) * (c - 1);
        -: 1953:	} else {
        -: 1954:	  for (unsigned int j = 0; j < c; j++) {
        -: 1955:		   SV**restrict val_sv = av_fetch(obs_av, j, 0);
        -: 1956:		   grand_total += SvNV(*val_sv);
        9: 1957:	  }
call    0 returned 9
        9: 1958:	  double E = grand_total / (double)c;
        -: 1959:	  for (unsigned int j = 0; j < c; j++) {
        9: 1960:		   SV**restrict val_sv = av_fetch(obs_av, j, 0);
        -: 1961:		   double O = SvNV(*val_sv);
        -: 1962:		   av_push(expected_av, newSVnv(E));
        -: 1963:		   stat += ((O - E) * (O - E)) / E;
        -: 1964:	  }
function XS_Stats__LikeR_write_table called 37 returned 84% blocks executed 78%
       37: 1965:	  df = c - 1;
        -: 1966:	}
       37: 1967:	double p_val = get_p_value(stat, df);
       37: 1967-block  0
call    0 returned 37
        -: 1968:	HV*restrict results = newHV();
        -: 1969:	hv_store(results, "statistic", 9, newSVnv(stat), 0);
        -: 1970:	hv_store(results, "df", 2, newSViv(df), 0);
       37: 1971:	hv_store(results, "p_value", 7, newSVnv(p_val), 0);
branch  0 taken 37 (fallthrough)
branch  1 taken 0
        -: 1972:	hv_store(results, "expected", 8, newRV_noinc((SV*)expected_av), 0);
        -: 1973:	if (is_2d) {
        -: 1974:		if (yates) {
        -: 1975:			hv_store(results, "method", 6, newSVpv("Pearson's Chi-squared test with Yates' continuity correction", 0), 0);
        -: 1976:		} else {
        -: 1977:			hv_store(results, "method", 6, newSVpv("Pearson's Chi-squared test", 0), 0);
        -: 1978:		}
        -: 1979:	} else {
        -: 1980:	  hv_store(results, "method", 6, newSVpv("Chi-squared test for given probabilities", 0), 0);
        -: 1981:	}
        -: 1982:	RETVAL = newRV_noinc((SV*)results);
        -: 1983:}
        -: 1984:#line 1985 "LikeR.c"
        -: 1985:	RETVAL = sv_2mortal(RETVAL);
        -: 1986:	ST(0) = RETVAL;
        -: 1987:    }
        -: 1988:    XSRETURN(1);
        -: 1989:}
        -: 1990:
        -: 1991:
        -: 1992:XS_EUPXS(XS_Stats__LikeR_write_table); /* prototype to pass -Wmissing-prototypes */
        -: 1993:XS_EUPXS(XS_Stats__LikeR_write_table)
        -: 1994:{
        -: 1995:    dVAR; dXSARGS;
        -: 1996:    PERL_UNUSED_VAR(cv); /* -W */
        -: 1997:    PERL_UNUSED_VAR(items); /* -W */
        -: 1998:    PERL_UNUSED_VAR(ax); /* -Wall */
        -: 1999:    SP -= items;
        -: 2000:    {
        -: 2001:#line 1799 "LikeR.xs"
        -: 2002:{
        -: 2003:	SV *restrict data_sv = NULL;
        -: 2004:	SV *restrict file_sv = NULL;
        -: 2005:	unsigned int arg_idx = 0;
        -: 2006:
        -: 2007:	// Mimic the Perl shift logic
        -: 2008:	if (arg_idx < items && SvROK(ST(arg_idx))) {
        -: 2009:	  int type = SvTYPE(SvRV(ST(arg_idx)));
        -: 2010:	  if (type == SVt_PVHV || type == SVt_PVAV) {
        -: 2011:		   data_sv = ST(arg_idx);
        -: 2012:		   arg_idx++;
        -: 2013:	  }
        -: 2014:	}
        -: 2015:	if (arg_idx < items) {
        -: 2016:	  file_sv = ST(arg_idx);
        -: 2017:	  arg_idx++;
        -: 2018:	}
        -: 2019:
        -: 2020:	const char *restrict sep = ",";
        -: 2021:	SV *restrict row_names_sv = sv_2mortal(newSViv(1));
        -: 2022:	SV *restrict col_names_sv = NULL;
        -: 2023:
        -: 2024:	// Read the remaining Hash-style arguments
        -: 2025:	for (; arg_idx < items; arg_idx += 2) {
        -: 2026:	  if (arg_idx + 1 >= items) croak("write_table: Odd number of arguments passed");
        -: 2027:	  const char *restrict key = SvPV_nolen(ST(arg_idx));
        -: 2028:	  SV *restrict val = ST(arg_idx + 1);
        -: 2029:	  if (strEQ(key, "data")) data_sv = val;
        -: 2030:	  else if (strEQ(key, "col.names")) col_names_sv = val;
        -: 2031:	  else if (strEQ(key, "file")) file_sv = val;
        -: 2032:	  else if (strEQ(key, "row.names")) row_names_sv = val;
        -: 2033:	  else if (strEQ(key, "sep")) sep = SvPV_nolen(val);
        -: 2034:	  else croak("write_table: Unknown arguments passed: %s", key);
        -: 2035:	}
        -: 2036:
        -: 2037:	if (!data_sv || !SvROK(data_sv)) {
        -: 2038:	  croak("write_table: 'data' must be a HASH or ARRAY reference\n");
        -: 2039:	}
        -: 2040:	SV *restrict data_ref = SvRV(data_sv);
        -: 2041:	if (SvTYPE(data_ref) != SVt_PVHV && SvTYPE(data_ref) != SVt_PVAV) {
        -: 2042:	  croak("write_table: 'data' must be a HASH or ARRAY reference\n");
        -: 2043:	}
        -: 2044:
        -: 2045:	if (!file_sv || !SvOK(file_sv)) croak("write_table: file name missing\n");
        -: 2046:	const char *restrict file = SvPV_nolen(file_sv);
        -: 2047:
        -: 2048:	if (col_names_sv && SvOK(col_names_sv)) {
        -: 2049:	  if (!SvROK(col_names_sv) || SvTYPE(SvRV(col_names_sv)) != SVt_PVAV) {
        -: 2050:		   croak("write_table: 'col.names' must be an ARRAY reference\n");
        -: 2051:	  }
        -: 2052:	}
        -: 2053:
        -: 2054:	bool is_hoh = 0, is_hoa = 0, is_aoh = 0;
        -: 2055:	AV *restrict rows_av = NULL;
        -: 2056:
        -: 2057:	// Validate Input Structures & Homogeneity 
        -: 2058:	if (SvTYPE(data_ref) == SVt_PVHV) {
        -: 2059:		HV *restrict hv = (HV*)data_ref;
        -: 2060:		if (hv_iterinit(hv) == 0) XSRETURN_EMPTY;
        -: 2061:
        -: 2062:		HE *restrict entry = hv_iternext(hv);
        -: 2063:		SV *restrict first_val = hv_iterval(hv, entry);
        -: 2064:		if (!first_val || !SvROK(first_val)) {
        -: 2065:			croak("write_table: Data values must be either all HASHes or all ARRAYs\n");
        -: 2066:		}
        -: 2067:		int first_type = SvTYPE(SvRV(first_val));
        -: 2068:		if (first_type != SVt_PVHV && first_type != SVt_PVAV) {
        -: 2069:			croak("write_table: Data values must be either all HASHes or all ARRAYs\n");
        -: 2070:		}
        -: 2071:		is_hoh = (first_type == SVt_PVHV);
        -: 2072:		is_hoa = (first_type == SVt_PVAV);
        -: 2073:		hv_iterinit(hv);
        -: 2074:		while ((entry = hv_iternext(hv))) {
        -: 2075:			SV *restrict val = hv_iterval(hv, entry);
        -: 2076:			if (!val || !SvROK(val) || SvTYPE(SvRV(val)) != first_type) {
        -: 2077:				 croak("write_table: Mixed data types detected. Ensure all values are %s references.\n", is_hoh ? "HASH" : "ARRAY");
        -: 2078:			}
        -: 2079:		}
        -: 2080:		if (is_hoh) {
        -: 2081:			rows_av = newAV();
        -: 2082:			hv_iterinit(hv);
        -: 2083:			while ((entry = hv_iternext(hv))) {
        -: 2084:				 av_push(rows_av, newSVsv(hv_iterkeysv(entry)));
        -: 2085:			}
        -: 2086:		}
        -: 2087:	} else {
        -: 2088:		AV *restrict av = (AV*)data_ref;
        -: 2089:		if (av_len(av) < 0) XSRETURN_EMPTY;
        -: 2090:		SV **restrict first_ptr = av_fetch(av, 0, 0);
        -: 2091:		if (!first_ptr || !*first_ptr || !SvROK(*first_ptr) || SvTYPE(SvRV(*first_ptr)) != SVt_PVHV) {
        -: 2092:			croak("write_table: For ARRAY data, all elements must be HASH references (Array of Hashes)\n");
        -: 2093:		}
        -: 2094:
        -: 2095:		for (size_t i = 0; i <= av_len(av); i++) {
        -: 2096:			SV **restrict ptr = av_fetch(av, i, 0);
        -: 2097:			if (!ptr || !*ptr || !SvROK(*ptr) || SvTYPE(SvRV(*ptr)) != SVt_PVHV) {
        -: 2098:				 croak("write_table: Mixed data types detected in Array of Hashes. All elements must be HASH references.\n");
        -: 2099:			}
        -: 2100:		}
        -: 2101:		is_aoh = 1;
        -: 2102:	}
        -: 2103:
        -: 2104:	PerlIO *restrict fh = PerlIO_open(file, "w");
        -: 2105:	if (!fh) croak("write_table: Could not open '%s' for writing", file);
        -: 2106:
        -: 2107:	AV *restrict headers_av = newAV();
        -: 2108:	bool inc_rownames = (row_names_sv && SvTRUE(row_names_sv)) ? 1 : 0;
        -: 2109:	const char *restrict rownames_col = NULL;
        -: 2110:
        -: 2111:	// ----- Hash of Hashes -----
        -: 2112:	if (is_hoh) {
        -: 2113:		if (col_names_sv && SvOK(col_names_sv)) {
        -: 2114:			AV *restrict c_av = (AV*)SvRV(col_names_sv);
        -: 2115:			for(size_t i=0; i<=av_len(c_av); i++) {
        -: 2116:				SV **restrict c = av_fetch(c_av, i, 0);
        -: 2117:				if(c && SvOK(*c)) av_push(headers_av, newSVsv(*c));
        -: 2118:			}
        -: 2119:		} else {
        -: 2120:			HV *restrict col_map = newHV();
        -: 2121:			hv_iterinit((HV*)data_ref);
        -: 2122:			HE *restrict entry;
        -: 2123:			while((entry = hv_iternext((HV*)data_ref))) {
        -: 2124:				HV *restrict inner = (HV*)SvRV(hv_iterval((HV*)data_ref, entry));
        -: 2125:				hv_iterinit(inner);
        -: 2126:				HE *restrict inner_entry;
        -: 2127:				while((inner_entry = hv_iternext(inner))) {
        -: 2128:					hv_store_ent(col_map, hv_iterkeysv(inner_entry), newSViv(1), 0);
        -: 2129:				}
        -: 2130:			}
        -: 2131:			unsigned num_cols = hv_iterinit(col_map);
        -: 2132:			const char **restrict col_array = safemalloc(num_cols * sizeof(char*));
        -: 2133:			for(unsigned i=0; i<num_cols; i++) {
        -: 2134:				HE *restrict ce = hv_iternext(col_map);
        -: 2135:				col_array[i] = SvPV_nolen(hv_iterkeysv(ce));
        -: 2136:			}
        -: 2137:			qsort(col_array, num_cols, sizeof(char*), cmp_string_wt);
        -: 2138:			for(unsigned i=0; i<num_cols; i++) av_push(headers_av, newSVpv(col_array[i], 0));
        -: 2139:			safefree(col_array);
        -: 2140:			SvREFCNT_dec(col_map);
        -: 2141:	  }
        -: 2142:	  size_t num_headers = av_len(headers_av) + 1;
        -: 2143:	  const char **restrict header_row = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2144:
        -: 2145:	  size_t h_idx = 0;
        -: 2146:	  if (inc_rownames) header_row[h_idx++] = "";
        -: 2147:	  for(unsigned short int i=0; i<num_headers; i++) {
        -: 2148:		   SV**restrict h_ptr = av_fetch(headers_av, i, 0);
        -: 2149:		   header_row[h_idx++] = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2150:	  }
        -: 2151:	  print_string_row(fh, header_row, h_idx, sep);
        -: 2152:	  safefree(header_row);
        -: 2153:
        -: 2154:	  size_t num_rows = av_len(rows_av) + 1;
        -: 2155:	  const char **restrict row_array = safemalloc(num_rows * sizeof(char*));
        -: 2156:	  for(size_t i=0; i<num_rows; i++) {
        -: 2157:	  	row_array[i] = SvPV_nolen(*av_fetch(rows_av, i, 0));
        -: 2158:	  }
        -: 2159:	  qsort(row_array, num_rows, sizeof(char*), cmp_string_wt);
        -: 2160:
        -: 2161:	  HV *restrict data_hv = (HV*)data_ref;
        -: 2162:	  const char **restrict row_data = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2163:
        -: 2164:	  for(size_t i=0; i<num_rows; i++) {
        -: 2165:		   size_t d_idx = 0;
        -: 2166:		   if (inc_rownames) row_data[d_idx++] = row_array[i];
        -: 2167:
        -: 2168:		   SV **restrict inner_hv_ptr = hv_fetch(data_hv, row_array[i], strlen(row_array[i]), 0);
        -: 2169:		   HV *restrict inner_hv = inner_hv_ptr ? (HV*)SvRV(*inner_hv_ptr) : NULL;
        -: 2170:
        -: 2171:		   for(size_t j=0; j<num_headers; j++) {
        -: 2172:		       SV**restrict h_ptr = av_fetch(headers_av, j, 0);
        -: 2173:		       const char *restrict col_name = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2174:		       SV **restrict cell_ptr = inner_hv ? hv_fetch(inner_hv, col_name, strlen(col_name), 0) : NULL;
        -: 2175:		       if (cell_ptr && SvOK(*cell_ptr)) {
        -: 2176:		           if (SvROK(*cell_ptr)) {
        -: 2177:		               PerlIO_close(fh);
        -: 2178:		               safefree(row_array);
        -: 2179:                     safefree(row_data);
        -: 2180:                       if (headers_av) SvREFCNT_dec(headers_av);
        -: 2181:                       if (rows_av) SvREFCNT_dec(rows_av);
        -: 2182:		               croak("write_table: Cannot write nested reference types to table\n");
        -: 2183:		           }
        -: 2184:		           row_data[d_idx++] = SvPV_nolen(*cell_ptr);
        -: 2185:		       } else {
        -: 2186:		           row_data[d_idx++] = "NA";
        -: 2187:		       }
        -: 2188:		   }
        -: 2189:		   print_string_row(fh, row_data, d_idx, sep);
        -: 2190:	  }
        -: 2191:	  safefree(row_array); safefree(row_data);
        -: 2192:
        -: 2193:	} else if (is_hoa) { // ----- Hash of Arrays -----
        -: 2194:		HV *restrict data_hv = (HV*)data_ref;
        -: 2195:		size_t max_rows = 0;
        -: 2196:		hv_iterinit(data_hv);
        -: 2197:		HE *restrict entry;
        -: 2198:		while((entry = hv_iternext(data_hv))) {
        -: 2199:			AV *restrict arr = (AV*)SvRV(hv_iterval(data_hv, entry));
        -: 2200:			size_t len = av_len(arr) + 1;
        -: 2201:			if (len > max_rows) max_rows = len;
        -: 2202:		}
        -: 2203:
        -: 2204:		if (col_names_sv && SvOK(col_names_sv)) {
        -: 2205:			AV *restrict c_av = (AV*)SvRV(col_names_sv);
        -: 2206:			for(size_t i=0; i<=av_len(c_av); i++) {
        -: 2207:				 SV **restrict c = av_fetch(c_av, i, 0);
        -: 2208:				 if(c && SvOK(*c)) av_push(headers_av, newSVsv(*c));
        -: 2209:			}
        -: 2210:		} else {
        -: 2211:			unsigned int num_cols = hv_iterinit(data_hv);
        -: 2212:			const char **restrict col_array = safemalloc(num_cols * sizeof(char*));
        -: 2213:			for(unsigned int i=0; i<num_cols; i++) {
        -: 2214:				 HE *restrict ce = hv_iternext(data_hv);
        -: 2215:				 col_array[i] = SvPV_nolen(hv_iterkeysv(ce));
        -: 2216:			}
        -: 2217:			qsort(col_array, num_cols, sizeof(char*), cmp_string_wt);
        -: 2218:			for(unsigned i=0; i<num_cols; i++) av_push(headers_av, newSVpv(col_array[i], 0));
        -: 2219:			safefree(col_array);
        -: 2220:		}
        -: 2221:		if (av_len(headers_av) < 0) croak("Could not get headers in write_table");
        -: 2222:		if (inc_rownames && contains_nondigit(row_names_sv)) {
        -: 2223:			rownames_col = SvPV_nolen(row_names_sv);
        -: 2224:			AV *restrict filtered_headers = (AV*)sv_2mortal((SV*)newAV());
        -: 2225:
        -: 2226:			for(size_t i=0; i<=av_len(headers_av); i++) {
        -: 2227:				 SV**restrict h_ptr = av_fetch(headers_av, i, 0);
        -: 2228:				 if (!h_ptr || !*h_ptr) continue;
        -: 2229:				 SV *restrict h_sv = *h_ptr;
        -: 2230:				 if (strcmp(SvPV_nolen(h_sv), rownames_col) != 0) {
        -: 2231:				     av_push(filtered_headers, newSVsv(h_sv));
        -: 2232:				 }
        -: 2233:			}
        -: 2234:			SvREFCNT_dec(headers_av);
        -: 2235:			headers_av = filtered_headers;
        -: 2236:		}
        -: 2237:		size_t num_headers = av_len(headers_av) + 1;
        -: 2238:		const char **restrict header_row = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2239:		size_t h_idx = 0;
        -: 2240:		if (inc_rownames) header_row[h_idx++] = "";
        -: 2241:		for(size_t i=0; i<num_headers; i++) {
        -: 2242:			SV**restrict h_ptr = av_fetch(headers_av, i, 0);
        -: 2243:			header_row[h_idx++] = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2244:		}
        -: 2245:		print_string_row(fh, header_row, h_idx, sep);
        -: 2246:		safefree(header_row);
        -: 2247:		const char **restrict row_data = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2248:		for(size_t i=0; i<max_rows; i++) {
        -: 2249:			size_t d_idx = 0;
        -: 2250:			if (inc_rownames) {
        -: 2251:				 if (rownames_col) {
        -: 2252:				     SV **restrict rn_arr_ptr = hv_fetch(data_hv, rownames_col, strlen(rownames_col), 0);
        -: 2253:				     if (rn_arr_ptr && SvROK(*rn_arr_ptr)) {
        -: 2254:				         AV *restrict rn_arr = (AV*)SvRV(*rn_arr_ptr);
        -: 2255:				         SV **restrict rn_val_ptr = av_fetch(rn_arr, i, 0);
        -: 2256:				         if (rn_val_ptr && SvOK(*rn_val_ptr)) {
        -: 2257:				             if (SvROK(*rn_val_ptr)) {
        -: 2258:				                 PerlIO_close(fh);
        -: 2259:				                 safefree(row_data);
        -: 2260:				                 if (headers_av) SvREFCNT_dec(headers_av);
        -: 2261:				                 croak("write_table: Cannot write nested reference types to table\n");
        -: 2262:				             }
        -: 2263:				             row_data[d_idx++] = SvPV_nolen(*rn_val_ptr);
        -: 2264:				         } else {
        -: 2265:				             row_data[d_idx++] = "NA";
        -: 2266:				         }
        -: 2267:				     } else {
        -: 2268:				         row_data[d_idx++] = "NA";
        -: 2269:				     }
        -: 2270:				 } else {
        -: 2271:				     char buf[32];
        -: 2272:				     snprintf(buf, sizeof(buf), "%ld", (long)(i + 1));
        -: 2273:				     row_data[d_idx++] = savepv(buf);
        -: 2274:				 }
        -: 2275:			}
        -: 2276:			for(size_t j=0; j<num_headers; j++) {
        -: 2277:				 SV**restrict h_ptr = av_fetch(headers_av, j, 0);
        -: 2278:				 const char *restrict col_name = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2279:				 SV **restrict arr_ptr = hv_fetch(data_hv, col_name, strlen(col_name), 0);
        -: 2280:				 if (arr_ptr && SvROK(*arr_ptr)) {
        -: 2281:				     AV *restrict arr = (AV*)SvRV(*arr_ptr);
        -: 2282:				     SV **restrict cell_ptr = av_fetch(arr, i, 0);
        -: 2283:				     if (cell_ptr && SvOK(*cell_ptr)) {
        -: 2284:				         if (SvROK(*cell_ptr)) {
        -: 2285:				             PerlIO_close(fh);
        -: 2286:				             safefree(row_data);
        -: 2287:				             if (headers_av) SvREFCNT_dec(headers_av);
        -: 2288:				             croak("write_table: Cannot write nested reference types to table\n");
        -: 2289:				         }
        -: 2290:				         row_data[d_idx++] = SvPV_nolen(*cell_ptr);
        -: 2291:				     } else {
        -: 2292:				         row_data[d_idx++] = "NA";
        -: 2293:				     }
        -: 2294:				 } else {
        -: 2295:				     row_data[d_idx++] = "NA";
        -: 2296:				 }
        -: 2297:			}
        -: 2298:			print_string_row(fh, row_data, d_idx, sep);
        -: 2299:			if (inc_rownames && !rownames_col) safefree((char*)row_data[0]);
        -: 2300:		}
        -: 2301:		safefree(row_data);
        -: 2302:	} else if (is_aoh) {// ----- Array of Hashes -----
        -: 2303:		AV *restrict data_av = (AV*)data_ref;
        -: 2304:		size_t num_rows = av_len(data_av) + 1;
        -: 2305:		if (col_names_sv && SvOK(col_names_sv)) {
        -: 2306:			AV *restrict c_av = (AV*)SvRV(col_names_sv);
        -: 2307:			for(size_t i=0; i<=av_len(c_av); i++) {
        -: 2308:				 SV **restrict c = av_fetch(c_av, i, 0);
        -: 2309:				 if(c && SvOK(*c)) av_push(headers_av, newSVsv(*c));
        -: 2310:			}
        -: 2311:		} else {
        -: 2312:			HV *restrict col_map = newHV();
        -: 2313:			for(size_t i=0; i<num_rows; i++) {
        -: 2314:				 SV **restrict row_ptr = av_fetch(data_av, i, 0);
        -: 2315:				 if (row_ptr && SvROK(*row_ptr)) {
        -: 2316:				     HV *restrict row_hv = (HV*)SvRV(*row_ptr);
        -: 2317:				     hv_iterinit(row_hv);
        -: 2318:				     HE *restrict entry;
        -: 2319:				     while((entry = hv_iternext(row_hv))) {
        -: 2320:				         hv_store_ent(col_map, hv_iterkeysv(entry), newSViv(1), 0);
        -: 2321:				     }
        -: 2322:				 }
        -: 2323:			}
        -: 2324:			unsigned num_cols = hv_iterinit(col_map);
        -: 2325:			const char **restrict col_array = safemalloc(num_cols * sizeof(char*));
        -: 2326:			for(unsigned int i=0; i<num_cols; i++) {
        -: 2327:				 HE *restrict ce = hv_iternext(col_map);
        -: 2328:				 col_array[i] = SvPV_nolen(hv_iterkeysv(ce));
        -: 2329:			}
        -: 2330:			qsort(col_array, num_cols, sizeof(char*), cmp_string_wt);
        -: 2331:			for(unsigned int i=0; i<num_cols; i++) av_push(headers_av, newSVpv(col_array[i], 0));
        -: 2332:			safefree(col_array);
        -: 2333:			SvREFCNT_dec(col_map);
        -: 2334:		}
        -: 2335:		if (inc_rownames && contains_nondigit(row_names_sv)) {
        -: 2336:			rownames_col = SvPV_nolen(row_names_sv);
        -: 2337:			AV *restrict filtered_headers = newAV();
        -: 2338:			for(size_t i=0; i<=av_len(headers_av); i++) {
        -: 2339:				 SV**restrict h_ptr = av_fetch(headers_av, i, 0);
        -: 2340:				 if (!h_ptr || !*h_ptr) continue;
        -: 2341:				 SV *restrict h_sv = *h_ptr;
        -: 2342:				 if (strcmp(SvPV_nolen(h_sv), rownames_col) != 0) {
        -: 2343:				     av_push(filtered_headers, newSVsv(h_sv));
        -: 2344:				 }
        -: 2345:			}
        -: 2346:			SvREFCNT_dec(headers_av);
        -: 2347:			headers_av = filtered_headers;
        -: 2348:		}
        -: 2349:		size_t num_headers = av_len(headers_av) + 1;
        -: 2350:		const char **restrict header_row = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2351:		size_t h_idx = 0;
        -: 2352:		if (inc_rownames) header_row[h_idx++] = "";
        -: 2353:		for(size_t i=0; i<num_headers; i++) {
        -: 2354:			SV**restrict h_ptr = av_fetch(headers_av, i, 0);
        -: 2355:			header_row[h_idx++] = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2356:		}
        -: 2357:		print_string_row(fh, header_row, h_idx, sep);
        -: 2358:		safefree(header_row);
        -: 2359:		const char **restrict row_data = safemalloc((num_headers + 1) * sizeof(char*));
        -: 2360:		for(size_t i=0; i<num_rows; i++) {
        -: 2361:			size_t d_idx = 0;
        -: 2362:			SV **restrict row_ptr = av_fetch(data_av, i, 0);
        -: 2363:			HV *restrict row_hv = (row_ptr && SvROK(*row_ptr)) ? (HV*)SvRV(*row_ptr) : NULL;
        -: 2364:			if (inc_rownames) {
        -: 2365:				if (rownames_col) {
        -: 2366:				  SV **restrict rn_val_ptr = row_hv ? hv_fetch(row_hv, rownames_col, strlen(rownames_col), 0) : NULL;
        -: 2367:				  if (rn_val_ptr && SvOK(*rn_val_ptr)) {
        -: 2368:						if (SvROK(*rn_val_ptr)) {
        -: 2369:							 PerlIO_close(fh);
        -: 2370:								  safefree(row_data);
        -: 2371:								  if (headers_av) SvREFCNT_dec(headers_av);
        -: 2372:							 croak("write_table: Cannot write nested reference types to table\n");
        -: 2373:						}
        -: 2374:						row_data[d_idx++] = SvPV_nolen(*rn_val_ptr);
        -: 2375:				  } else {
        -: 2376:						row_data[d_idx++] = "NA";
        -: 2377:				  }
        -: 2378:				} else {
        -: 2379:				  char buf[32];
        -: 2380:				  snprintf(buf, sizeof(buf), "%ld", (long)(i + 1));
        -: 2381:				  row_data[d_idx++] = savepv(buf);
        -: 2382:				}
        -: 2383:			}
        -: 2384:
        -: 2385:			for(size_t j=0; j<num_headers; j++) {
        -: 2386:				 SV**restrict h_ptr = av_fetch(headers_av, j, 0);
        -: 2387:				 const char *restrict col_name = (h_ptr && SvOK(*h_ptr)) ? SvPV_nolen(*h_ptr) : "";
        -: 2388:				 SV **restrict cell_ptr = row_hv ? hv_fetch(row_hv, col_name, strlen(col_name), 0) : NULL;
        -: 2389:				 if (cell_ptr && SvOK(*cell_ptr)) {
        -: 2390:				     if (SvROK(*cell_ptr)) {
        -: 2391:				         PerlIO_close(fh);
        -: 2392:                         safefree(row_data);
        -: 2393:                         if (headers_av) SvREFCNT_dec(headers_av);
function XS_Stats__LikeR__parse_csv_file called 39 returned 100% blocks executed 86%
       39: 2394:				         croak("write_table: Cannot write nested reference types to table\n");
        -: 2395:				     }
       39: 2396:				     row_data[d_idx++] = SvPV_nolen(*cell_ptr);
       39: 2396-block  0
call    0 returned 39
       39: 2397:				 } else {
branch  0 taken 39 (fallthrough)
branch  1 taken 0
       39: 2397-block  0
branch  2 taken 0 (fallthrough)
branch  3 taken 39
    #####: 2398:				     row_data[d_idx++] = "NA";
    %%%%%: 2398-block  0
call    0 never executed
        -: 2399:				 }
        -: 2400:			}
       39: 2401:			print_string_row(fh, row_data, d_idx, sep);
       39: 2401-block  0
call    0 returned 39
        -: 2402:			if (inc_rownames && !rownames_col) safefree((char*)row_data[0]);
       39: 2403:		}
call    0 returned 39
        -: 2404:		safefree(row_data);
       39: 2405:	}
call    0 returned 39
        -: 2406:	if (headers_av) SvREFCNT_dec(headers_av);
        -: 2407:	if (rows_av) SvREFCNT_dec(rows_av);
        -: 2408:	PerlIO_close(fh);
       39: 2409:	XSRETURN_EMPTY;
branch  0 taken 0 (fallthrough)
branch  1 taken 39
    #####: 2410:}
    %%%%%: 2410-block  0
        -: 2411:#line 2412 "LikeR.c"
       39: 2412:	PUTBACK;
       39: 2412-block  0
        -: 2413:	return;
        -: 2414:    }
        -: 2415:}
        -: 2416:
        -: 2417:
        -: 2418:XS_EUPXS(XS_Stats__LikeR__parse_csv_file); /* prototype to pass -Wmissing-prototypes */
        -: 2419:XS_EUPXS(XS_Stats__LikeR__parse_csv_file)
        -: 2420:{
        -: 2421:    dVAR; dXSARGS;
        -: 2422:    if (items < 3 || items > 4)
        -: 2423:       croak_xs_usage(cv,  "file, sep_str, comment_str, callback= &PL_sv_undef");
        -: 2424:    {
        -: 2425:	SV *	RETVAL;
        -: 2426:	char*	file = (char *)SvPV_nolen(ST(0))
        -: 2427:;
        -: 2428:	const char*	sep_str = (const char *)SvPV_nolen(ST(1))
        -: 2429:;
        -: 2430:	const char*	comment_str = (const char *)SvPV_nolen(ST(2))
        -: 2431:;
        -: 2432:	SV*	callback;
        -: 2433:
        -: 2434:	if (items < 4)
        -: 2435:	    callback = &PL_sv_undef;
        -: 2436:	else {
        -: 2437:	    callback = ST(3)
        -: 2438:;
        -: 2439:	}
        -: 2440:#line 2212 "LikeR.xs"
        -: 2441:	PerlIO *restrict fp;
        -: 2442:	AV *restrict data = NULL;
        -: 2443:	AV *restrict current_row = newAV();
        -: 2444:	SV *restrict field = newSVpvs("");
        -: 2445:	bool in_quotes = 0, post_quote = 0;
        -: 2446:	size_t sep_len, comment_len;
        -: 2447:	SV *restrict line_sv;
        -: 2448:	bool use_cb = 0;
        -: 2449:#line 2450 "LikeR.c"
        -: 2450:#line 2221 "LikeR.xs"
        -: 2451:	if (SvOK(callback) && SvROK(callback) && SvTYPE(SvRV(callback)) == SVt_PVCV) {
        -: 2452:		use_cb = 1;
        -: 2453:	} else {
        -: 2454:		data = newAV();
        -: 2455:	}
        -: 2456:	sep_len = sep_str ? strlen(sep_str) : 0;
        -: 2457:	comment_len = comment_str ? strlen(comment_str) : 0;
        -: 2458:
        -: 2459:	fp = PerlIO_open(file, "r");
        -: 2460:	if (!fp) {
        -: 2461:		croak("Could not open file '%s'", file);
        -: 2462:	}
        -: 2463:	line_sv = newSV_type(SVt_PV);
        -: 2464:	// Read line by line using PerlIO
        -: 2465:	while (sv_gets(line_sv, fp, 0) != NULL) {
        -: 2466:		char *restrict line = SvPV_nolen(line_sv);
        -: 2467:		size_t len = SvCUR(line_sv);
        -: 2468:		// chomp \r\n (Handles Windows invisible \r natively)
        -: 2469:		if (len > 0 && line[len-1] == '\n') {
        -: 2470:			len--;
        -: 2471:			if (len > 0 && line[len-1] == '\r') {
        -: 2472:				len--;
        -: 2473:			}
        -: 2474:		}
        -: 2475:		if (!in_quotes) {
        -: 2476:			// Skip completely empty lines (\h*[\r\n]+$ equivalent)
        -: 2477:			bool is_empty = 1;
        -: 2478:			for (size_t i = 0; i < len; i++) {
        -: 2479:				if (line[i] != ' ' && line[i] != '\t') { is_empty = 0; break; }
        -: 2480:			}
        -: 2481:			if (is_empty) continue;
        -: 2482:
        -: 2483:			// Skip comments
        -: 2484:			if (comment_len > 0 && len >= comment_len && strncmp(line, comment_str, comment_len) == 0) {
        -: 2485:				continue;
        -: 2486:			}
        -: 2487:		}
        -: 2488:		// --- CORE PARSING MACHINE ---
        -: 2489:		for (size_t i = 0; i < len; i++) {
        -: 2490:			const char ch = line[i];
        -: 2491:			if (ch == '\r') continue;
        -: 2492:			if (ch == '"') {
        -: 2493:				if (in_quotes && (i + 1 < len) && line[i+1] == '"') {
        -: 2494:					sv_catpvn(field, "\"", 1);
        -: 2495:					i++; // Skip the escaped second quote
        -: 2496:				} else if (in_quotes) {
        -: 2497:					in_quotes = 0;  // Close quotes
        -: 2498:					post_quote = 1;
        -: 2499:				} else if (!post_quote) {
        -: 2500:					in_quotes = 1; // Open quotes (only when not in post-quote state)
        -: 2501:				}
        -: 2502:			} else if (!in_quotes && sep_len > 0 && (len - i) >= sep_len && strncmp(line + i, sep_str, sep_len) == 0) {
        -: 2503:				av_push(current_row, newSVsv(field));
        -: 2504:				sv_setpvs(field, ""); // Reset for next field
        -: 2505:				i += sep_len - 1;     // Advance past multi-char separators
        -: 2506:				post_quote = 0;
        -: 2507:			} else {
        -: 2508:				sv_catpvn(field, &ch, 1);
        -: 2509:			}
        -: 2510:		}
        -: 2511:		if (in_quotes) {
        -: 2512:			// Line ended but quotes are still open! Append newline and fetch next
        -: 2513:			sv_catpvn(field, "\n", 1);
        -: 2514:		} else {
        -: 2515:			post_quote = 0; // Reset post-quote state at row boundary
        -: 2516:			// Push the final field of the record
        -: 2517:			av_push(current_row, newSVsv(field));
        -: 2518:			sv_setpvs(field, "");
        -: 2519:			// If a callback is provided, invoke it in a streaming fashion
        -: 2520:			if (use_cb) {
        -: 2521:				dSP;
        -: 2522:				ENTER;
        -: 2523:				SAVETMPS;
        -: 2524:				PUSHMARK(SP);
        -: 2525:				XPUSHs(sv_2mortal(newRV_inc((SV*)current_row)));
        -: 2526:				PUTBACK;
        -: 2527:				call_sv(callback, G_DISCARD);
        -: 2528:				FREETMPS;
        -: 2529:				LEAVE;
        -: 2530:				SvREFCNT_dec(current_row); // Frees the row from C memory if Perl didn't keep it
        -: 2531:			} else {
        -: 2532:				av_push(data, newRV_noinc((SV*)current_row));
        -: 2533:			}
        -: 2534:			current_row = newAV();
        -: 2535:		}
        -: 2536:	}
        -: 2537:	PerlIO_close(fp);
        -: 2538:	SvREFCNT_dec(line_sv);
        -: 2539:
        -: 2540:	if (in_quotes) {
        -: 2541:		av_push(current_row, newSVsv(field));
       39: 2542:		if (use_cb) {
       39: 2542-block  0
call    0 returned 39
       39: 2543:			dSP;
        -: 2544:			ENTER;
       39: 2545:			SAVETMPS;
        -: 2546:			PUSHMARK(SP);
        -: 2547:			XPUSHs(sv_2mortal(newRV_inc((SV*)current_row)));
        -: 2548:			PUTBACK;
        -: 2549:			call_sv(callback, G_DISCARD);
function XS_Stats__LikeR_cov called 12 returned 100% blocks executed 86%
       12: 2550:			FREETMPS;
        -: 2551:			LEAVE;
       12: 2552:			SvREFCNT_dec(current_row);
       12: 2552-block  0
call    0 returned 12
       12: 2553:		} else {
branch  0 taken 12 (fallthrough)
branch  1 taken 0
       12: 2553-block  0
branch  2 taken 0 (fallthrough)
branch  3 taken 12
    #####: 2554:			av_push(data, newRV_noinc((SV*)current_row));
    %%%%%: 2554-block  0
call    0 never executed
        -: 2555:		}
        -: 2556:		current_row = newAV();
       12: 2557:	}
        -: 2558:	SvREFCNT_dec(field);
       12: 2559:	SvREFCNT_dec(current_row);
        -: 2560:	if (use_cb) {
        -: 2561:		RETVAL = &PL_sv_undef; // Memory was fully handled by callback stream
        -: 2562:	} else {
       12: 2563:		RETVAL = newRV_noinc((SV*)data);
       12: 2563-block  0
branch  0 taken 3 (fallthrough)
branch  1 taken 9
        3: 2564:	}
        3: 2564-block  0
        -: 2565:#line 2566 "LikeR.c"
        9: 2566:	RETVAL = sv_2mortal(RETVAL);
        9: 2566-block  0
call    0 returned 9
        -: 2567:	ST(0) = RETVAL;
        -: 2568:    }
        -: 2569:    XSRETURN(1);
        -: 2570:}
        -: 2571:
        -: 2572:
        -: 2573:XS_EUPXS(XS_Stats__LikeR_cov); /* prototype to pass -Wmissing-prototypes */
        -: 2574:XS_EUPXS(XS_Stats__LikeR_cov)
        -: 2575:{
        -: 2576:    dVAR; dXSARGS;
        -: 2577:    if (items < 2 || items > 3)
        -: 2578:       croak_xs_usage(cv,  "x_sv, y_sv, method= \"pearson\"");
        -: 2579:    {
        -: 2580:	SV *	RETVAL;
        -: 2581:	SV *	x_sv = ST(0)
        -: 2582:;
        -: 2583:	SV *	y_sv = ST(1)
        -: 2584:;
        -: 2585:	const char *	method;
        -: 2586:
        -: 2587:	if (items < 3)
        -: 2588:	    method = "pearson";
        -: 2589:	else {
        -: 2590:	    method = (const char *)SvPV_nolen(ST(2))
        -: 2591:;
        -: 2592:	}
        -: 2593:#line 2340 "LikeR.xs"
        -: 2594:	{
        -: 2595:		// 1. Validate inputs are Array References
        -: 2596:		if (!SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV) {
        -: 2597:			croak("cov: first argument 'x' must be an ARRAY reference");
        -: 2598:		}
        -: 2599:		if (!SvROK(y_sv) || SvTYPE(SvRV(y_sv)) != SVt_PVAV) {
        -: 2600:			croak("cov: second argument 'y' must be an ARRAY reference");
        -: 2601:		}
        -: 2602:
        -: 2603:		// 2. Validate method argument
        -: 2604:		if (strcmp(method, "pearson") != 0 && 
        -: 2605:			strcmp(method, "spearman") != 0 && 
        -: 2606:			strcmp(method, "kendall") != 0) {
        -: 2607:			croak("cov: unknown method '%s' (use 'pearson', 'spearman', or 'kendall')", method);
        -: 2608:		}
        -: 2609:
        -: 2610:		AV *restrict x_av = (AV*)SvRV(x_sv);
        -: 2611:		AV *restrict y_av = (AV*)SvRV(y_sv);
        -: 2612:		size_t nx = av_len(x_av) + 1;
        -: 2613:		size_t ny = av_len(y_av) + 1;
        -: 2614:
        -: 2615:		if (nx != ny) {
        -: 2616:			croak("cov: incompatible dimensions (x has %lu, y has %lu)", 
        -: 2617:				   (unsigned long)nx, (unsigned long)ny);
        -: 2618:		}
        -: 2619:
        -: 2620:		// 3. Extract Valid Pairwise Data
        -: 2621:		// Allocate temporary C arrays for numeric processing
        -: 2622:		double *restrict x_val = (double*)safemalloc(nx * sizeof(double));
        -: 2623:		double *restrict y_val = (double*)safemalloc(nx * sizeof(double));
        -: 2624:		size_t n = 0;
        -: 2625:
        -: 2626:		for (size_t i = 0; i < nx; i++) {
        -: 2627:			SV **restrict x_tv = av_fetch(x_av, i, 0);
        -: 2628:			SV **restrict y_tv = av_fetch(y_av, i, 0);
        -: 2629:
        -: 2630:			// Extract numeric values, defaulting to NAN for missing/invalid data
        -: 2631:			double xv = (x_tv && SvOK(*x_tv) && looks_like_number(*x_tv)) ? SvNV(*x_tv) : NAN;
        -: 2632:			double yv = (y_tv && SvOK(*y_tv) && looks_like_number(*y_tv)) ? SvNV(*y_tv) : NAN;
        -: 2633:
        -: 2634:			// Pairwise complete observations (skips NAs seamlessly like R)
        -: 2635:			if (!isnan(xv) && !isnan(yv)) {
        -: 2636:				 x_val[n] = xv;
        -: 2637:				 y_val[n] = yv;
        -: 2638:				 n++;
        -: 2639:			}
        -: 2640:		}
        -: 2641:
        -: 2642:		// 4. Handle edge cases where data is too sparse
        -: 2643:		if (n < 2) {
        -: 2644:			Safefree(x_val);	Safefree(y_val);
        -: 2645:			RETVAL = newSVnv(NAN);
        -: 2646:		} else {
        -: 2647:			double ans = 0.0;			
        -: 2648:			// 5. Algorithm routing
        -: 2649:			if (strcmp(method, "kendall") == 0) {
        -: 2650:				 // R's default cov(..., method="kendall") iterates the full n x n space
        -: 2651:				 for (size_t i = 0; i < n; i++) {
        -: 2652:				     for (size_t j = 0; j < n; j++) {
        -: 2653:				         int sx = (x_val[i] > x_val[j]) - (x_val[i] < x_val[j]);
        -: 2654:				         int sy = (y_val[i] > y_val[j]) - (y_val[i] < y_val[j]);
        -: 2655:				         ans += (double)(sx * sy);
        -: 2656:				     }
        -: 2657:				 }
        -: 2658:			} else {
        -: 2659:				 double mean_x = 0.0, mean_y = 0.0, cov_sum = 0.0;
        -: 2660:				 if (strcmp(method, "spearman") == 0) {
        -: 2661:				     // Spearman: Rank the data first, then run standard covariance
        -: 2662:				     double *restrict rx = (double*)safemalloc(n * sizeof(double));
        -: 2663:				     double *restrict ry = (double*)safemalloc(n * sizeof(double));
        -: 2664:
        -: 2665:				     // Uses your existing rank_data() helper from LikeR.xs
        -: 2666:				     rank_data(x_val, rx, n);
        -: 2667:				     rank_data(y_val, ry, n);
        -: 2668:
        -: 2669:				     for (size_t i = 0; i < n; i++) {
        -: 2670:				         double dx = rx[i] - mean_x;
        -: 2671:				         mean_x += dx / (i + 1);
        -: 2672:				         double dy = ry[i] - mean_y;
        -: 2673:				         mean_y += dy / (i + 1);
        -: 2674:				         cov_sum += dx * (ry[i] - mean_y);
        -: 2675:				     }
        -: 2676:
        -: 2677:				     Safefree(rx);
        -: 2678:				     Safefree(ry);
       12: 2679:				 } else { 
       12: 2679-block  0
call    0 returned 12
       12: 2680:				     // Pearson: Welford's Single-Pass Covariance Algorithm
        -: 2681:				     for (size_t i = 0; i < n; i++) {
       12: 2682:				         double dx = x_val[i] - mean_x;
        -: 2683:				         mean_x += dx / (i + 1);
        -: 2684:				         double dy = y_val[i] - mean_y;
        -: 2685:				         mean_y += dy / (i + 1);
        -: 2686:				         cov_sum += dx * (y_val[i] - mean_y);
function XS_Stats__LikeR_glm called 21 returned 100% blocks executed 79%
       21: 2687:				     }
        -: 2688:				 }
       21: 2689:
       21: 2689-block  0
call    0 returned 21
branch  1 taken 0 (fallthrough)
branch  2 taken 21
        -: 2690:				 // Unbiased Sample Covariance (N - 1) for Pearson & Spearman
        -: 2691:				 ans = cov_sum / (n - 1);
        -: 2692:			}
        -: 2693:
        -: 2694:			Safefree(x_val);
        -: 2695:			Safefree(y_val);
        -: 2696:			RETVAL = newSVnv(ans);
        -: 2697:		}
        -: 2698:	}
        -: 2699:#line 2700 "LikeR.c"
        -: 2700:	RETVAL = sv_2mortal(RETVAL);
        -: 2701:	ST(0) = RETVAL;
        -: 2702:    }
        -: 2703:    XSRETURN(1);
        -: 2704:}
        -: 2705:
        -: 2706:
        -: 2707:XS_EUPXS(XS_Stats__LikeR_glm); /* prototype to pass -Wmissing-prototypes */
        -: 2708:XS_EUPXS(XS_Stats__LikeR_glm)
        -: 2709:{
        -: 2710:    dVAR; dXSARGS;
        -: 2711:    PERL_UNUSED_VAR(cv); /* -W */
        -: 2712:    PERL_UNUSED_VAR(items); /* -W */
        -: 2713:    {
        -: 2714:	SV *	RETVAL;
        -: 2715:#line 2450 "LikeR.xs"
        -: 2716:{
        -: 2717:	const char *restrict formula  = NULL;
        -: 2718:	SV *restrict data_sv = NULL;
        -: 2719:	const char *restrict family_str = "gaussian";
        -: 2720:	char f_cpy[512];
        -: 2721:	char *restrict src, *restrict dst, *restrict tilde, *restrict lhs, *restrict rhs, *restrict chunk;
        -: 2722:
        -: 2723:	// Dynamic Term Arrays
        -: 2724:	char **restrict terms = NULL, **restrict uniq_terms = NULL, **restrict exp_terms = NULL;
        -: 2725:	bool *restrict is_dummy = NULL;
        -: 2726:	char **restrict dummy_base = NULL, **restrict dummy_level = NULL;
        -: 2727:	unsigned int term_cap = 64, exp_cap = 64, num_terms = 0, num_uniq = 0, p = 0, p_exp = 0;
        -: 2728:	size_t n = 0, valid_n = 0, i;
        -: 2729:	bool has_intercept = true, converged = false, boundary = false;
        -: 2730:	int iter = 0, max_iter = 25, final_rank = 0, df_res = 0;
        -: 2731:	double deviance_old = 0.0, deviance_new = 0.0, null_dev = 0.0, aic = 0.0;
        -: 2732:	double dispersion = 0.0, epsilon = 1e-8;
        -: 2733:
        -: 2734:	char **restrict row_names = NULL;
        -: 2735:	char **restrict valid_row_names = NULL;
        -: 2736:	HV **restrict row_hashes = NULL;
        -: 2737:	HV *restrict data_hoa = NULL;
        -: 2738:	SV *restrict ref = NULL;
        -: 2739:
        -: 2740:	double *restrict X = NULL, *restrict Y = NULL, *restrict mu = NULL, *restrict eta = NULL;
        -: 2741:	double *restrict W = NULL, *restrict Z = NULL, *restrict beta = NULL, *restrict beta_old = NULL;
        -: 2742:	bool *restrict aliased = NULL;
        -: 2743:	double *restrict XtWX = NULL, *restrict XtWZ = NULL;
        -: 2744:
        -: 2745:	HV *restrict res_hv, *restrict coef_hv, *restrict fitted_hv, *restrict resid_hv, *restrict summary_hv;
        -: 2746:	AV *restrict terms_av;
        -: 2747:	HE *restrict entry;
        -: 2748:
        -: 2749:	if (items % 2 != 0) croak("Usage: glm(formula => 'am ~ wt + hp', data => \\%mtcars)");
        -: 2750:
        -: 2751:	for (unsigned short i_arg = 0; i_arg < items; i_arg += 2) {
        -: 2752:	  const char *restrict key = SvPV_nolen(ST(i_arg));
        -: 2753:	  SV *restrict val = ST(i_arg + 1);
        -: 2754:	  if      (strEQ(key, "formula")) formula = SvPV_nolen(val);
        -: 2755:	  else if (strEQ(key, "data"))    data_sv = val;
        -: 2756:	  else if (strEQ(key, "family"))  family_str = SvPV_nolen(val);
        -: 2757:	  else croak("glm: unknown argument '%s'", key);
        -: 2758:	}        
        -: 2759:	if (!formula) croak("glm: formula is required");
        -: 2760:	if (!data_sv || !SvROK(data_sv)) croak("glm: data is required and must be a reference");
        -: 2761:
        -: 2762:	bool is_binomial = (strcmp(family_str, "binomial") == 0);
        -: 2763:	bool is_gaussian = (strcmp(family_str, "gaussian") == 0);
        -: 2764:	if (!is_binomial && !is_gaussian) croak("glm: unsupported family '%s'", family_str);
        -: 2765:
        -: 2766:	// --- Formula Parsing & Expansion ---
        -: 2767:	Newx(terms, term_cap, char*); Newx(uniq_terms, term_cap, char*);
        -: 2768:	Newx(exp_terms, exp_cap, char*); Newx(is_dummy, exp_cap, bool);
        -: 2769:	Newx(dummy_base, exp_cap, char*); Newx(dummy_level, exp_cap, char*);
        -: 2770:
        -: 2771:	src = (char*)formula; dst = f_cpy;
        -: 2772:	while (*src && (dst - f_cpy < 511)) { if (!isspace(*src)) { *dst++ = *src; } src++; }
        -: 2773:	*dst = '\0';
        -: 2774:
        -: 2775:	tilde = strchr(f_cpy, '~');
        -: 2776:	if (!tilde) croak("glm: invalid formula, missing '~'");
        -: 2777:	*tilde = '\0';
        -: 2778:	lhs = f_cpy; rhs = tilde + 1;
        -: 2779:
        -: 2780:	if (strstr(rhs, "-1")) has_intercept = false;
        -: 2781:	if (has_intercept) terms[num_terms++] = savepv("Intercept");
        -: 2782:
        -: 2783:	chunk = strtok(rhs, "+");
        -: 2784:	while (chunk != NULL) {
        -: 2785:	  if (num_terms >= term_cap - 3) {
        -: 2786:		   term_cap *= 2;
        -: 2787:		   Renew(terms, term_cap, char*); Renew(uniq_terms, term_cap, char*);
        -: 2788:	  }
        -: 2789:	  if (strcmp(chunk, "1") == 0 || strcmp(chunk, "-1") == 0) {
        -: 2790:		   chunk = strtok(NULL, "+");
        -: 2791:		   continue;
        -: 2792:	  }
        -: 2793:	  char *restrict star = strchr(chunk, '*');
        -: 2794:	  if (star) {
        -: 2795:		   *star = '\0';
        -: 2796:		   char *restrict left = chunk; char *restrict right = star + 1;
        -: 2797:		   char *restrict c_l = strchr(left, '^'); if (c_l && strncmp(left, "I(", 2) != 0) *c_l = '\0';
        -: 2798:		   char *restrict c_r = strchr(right, '^'); if (c_r && strncmp(right, "I(", 2) != 0) *c_r = '\0';
        -: 2799:
        -: 2800:		   terms[num_terms++] = savepv(left);
        -: 2801:		   terms[num_terms++] = savepv(right);
        -: 2802:		   size_t inter_len = strlen(left) + strlen(right) + 2;
        -: 2803:		   terms[num_terms] = (char*)safemalloc(inter_len);
        -: 2804:		   snprintf(terms[num_terms++], inter_len, "%s:%s", left, right);
        -: 2805:	  } else {
        -: 2806:		   char *restrict c_chunk = strchr(chunk, '^'); 
        -: 2807:		   if (c_chunk && strncmp(chunk, "I(", 2) != 0) *c_chunk = '\0';
        -: 2808:		   terms[num_terms++] = savepv(chunk);
        -: 2809:	  }
        -: 2810:	  chunk = strtok(NULL, "+");
        -: 2811:	}
        -: 2812:
        -: 2813:	for (i = 0; i < num_terms; i++) {
        -: 2814:	  bool found = false;
        -: 2815:	  for (size_t j = 0; j < num_uniq; j++) {
        -: 2816:		   if (strcmp(terms[i], uniq_terms[j]) == 0) { found = true; break; }
        -: 2817:	  }
        -: 2818:	  if (!found) uniq_terms[num_uniq++] = savepv(terms[i]);
        -: 2819:	}
        -: 2820:	p = num_uniq;
        -: 2821:
        -: 2822:	// --- Data Extraction ---
        -: 2823:	ref = SvRV(data_sv);
        -: 2824:	if (SvTYPE(ref) == SVt_PVHV) {
        -: 2825:		HV*restrict hv = (HV*)ref;
        -: 2826:		if (hv_iterinit(hv) == 0) croak("glm: Data hash is empty");
        -: 2827:		entry = hv_iternext(hv);
        -: 2828:		if (entry) {
        -: 2829:			SV*restrict val = hv_iterval(hv, entry);
        -: 2830:			if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
        -: 2831:				 data_hoa = hv;
        -: 2832:				 n = av_len((AV*)SvRV(val)) + 1;
        -: 2833:				 Newx(row_names, n, char*);
        -: 2834:				 for(i = 0; i < n; i++) {
        -: 2835:				     char buf[32]; snprintf(buf, sizeof(buf), "%lu", i+1);
        -: 2836:				     row_names[i] = savepv(buf);
        -: 2837:				 }
        -: 2838:			} else if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVHV) {
        -: 2839:				 n = hv_iterinit(hv);
        -: 2840:				 Newx(row_names, n, char*); Newx(row_hashes, n, HV*);
        -: 2841:				 i = 0;
        -: 2842:				 while ((entry = hv_iternext(hv))) {
        -: 2843:				     unsigned int len;
        -: 2844:				     row_names[i] = savepv(hv_iterkey(entry, &len));
        -: 2845:				     row_hashes[i] = (HV*)SvRV(hv_iterval(hv, entry));
        -: 2846:				     i++;
        -: 2847:				 }
        -: 2848:			} else croak("glm: Hash values must be ArrayRefs (HoA) or HashRefs (HoH)");
        -: 2849:		}
        -: 2850:	} else if (SvTYPE(ref) == SVt_PVAV) {
        -: 2851:	  AV*restrict av = (AV*)ref;
        -: 2852:	  n = av_len(av) + 1;
        -: 2853:	  Newx(row_names, n, char*); Newx(row_hashes, n, HV*);
        -: 2854:	  for (i = 0; i < n; i++) {
        -: 2855:		   SV**restrict val = av_fetch(av, i, 0);
        -: 2856:		   if (val && SvROK(*val) && SvTYPE(SvRV(*val)) == SVt_PVHV) {
        -: 2857:		       row_hashes[i] = (HV*)SvRV(*val);
        -: 2858:		       char buf[32]; snprintf(buf, sizeof(buf), "%lu", i + 1);
        -: 2859:		       row_names[i] = savepv(buf);
        -: 2860:		   } else {
        -: 2861:		       for (size_t k = 0; k < i; k++) Safefree(row_names[k]);
        -: 2862:		       Safefree(row_names); Safefree(row_hashes);
        -: 2863:		       croak("glm: Array values must be HashRefs (AoH)");
        -: 2864:		   }
        -: 2865:	  }
        -: 2866:	} else croak("glm: Data must be an Array or Hash reference");
        -: 2867:
        -: 2868:	// --- Categorical Expansion ---
        -: 2869:	for (size_t j = 0; j < p; j++) {
        -: 2870:	  if (p_exp + 32 >= exp_cap) {
        -: 2871:		   exp_cap *= 2;
        -: 2872:		   Renew(exp_terms, exp_cap, char*); Renew(is_dummy, exp_cap, bool);
        -: 2873:		   Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 2874:	  }
        -: 2875:	  if (strcmp(uniq_terms[j], "Intercept") == 0) {
        -: 2876:		   exp_terms[p_exp] = savepv("Intercept"); is_dummy[p_exp] = false; p_exp++; continue;
        -: 2877:	  }
        -: 2878:	  if (is_column_categorical(data_hoa, row_hashes, n, uniq_terms[j])) {
        -: 2879:		   char **restrict levels = NULL; size_t num_levels = 0, levels_cap = 8;
        -: 2880:		   Newx(levels, levels_cap, char*);
        -: 2881:		   for (i = 0; i < n; i++) {
        -: 2882:		       char*restrict str_val = get_data_string_alloc(data_hoa, row_hashes, i, uniq_terms[j]);
        -: 2883:		       if (str_val) {
        -: 2884:		           bool found = false;
        -: 2885:		           for (size_t l = 0; l < num_levels; l++) {
        -: 2886:		               if (strcmp(levels[l], str_val) == 0) { found = true; break; }
        -: 2887:		           }
        -: 2888:		           if (!found) {
        -: 2889:		               if (num_levels >= levels_cap) { levels_cap *= 2; Renew(levels, levels_cap, char*); }
        -: 2890:		               levels[num_levels++] = savepv(str_val);
        -: 2891:		           }
        -: 2892:		           Safefree(str_val);
        -: 2893:		       }
        -: 2894:		   }
        -: 2895:		   if (num_levels > 0) {
        -: 2896:		       for (size_t l1 = 0; l1 < num_levels - 1; l1++) {
        -: 2897:		           for (size_t l2 = l1 + 1; l2 < num_levels; l2++) {
        -: 2898:		               if (strcmp(levels[l1], levels[l2]) > 0) {
        -: 2899:		                   char *tmp = levels[l1]; levels[l1] = levels[l2]; levels[l2] = tmp;
        -: 2900:		               }
        -: 2901:		           }
        -: 2902:		       }
        -: 2903:		       for (size_t l = 1; l < num_levels; l++) {
        -: 2904:		           if (p_exp >= exp_cap) {
        -: 2905:		               exp_cap *= 2;
        -: 2906:		               Renew(exp_terms, exp_cap, char*); Renew(is_dummy, exp_cap, bool);
        -: 2907:		               Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 2908:		           }
        -: 2909:		           size_t t_len = strlen(uniq_terms[j]) + strlen(levels[l]) + 1;
        -: 2910:		           exp_terms[p_exp] = (char*)safemalloc(t_len);
        -: 2911:		           snprintf(exp_terms[p_exp], t_len, "%s%s", uniq_terms[j], levels[l]);
        -: 2912:		           is_dummy[p_exp] = true; dummy_base[p_exp] = savepv(uniq_terms[j]); dummy_level[p_exp] = savepv(levels[l]);
        -: 2913:		           p_exp++;
        -: 2914:		       }
        -: 2915:		       for (size_t l = 0; l < num_levels; l++) Safefree(levels[l]);
        -: 2916:		       Safefree(levels);
        -: 2917:		   } else {
        -: 2918:		       Safefree(levels); exp_terms[p_exp] = savepv(uniq_terms[j]); is_dummy[p_exp] = false; p_exp++;
        -: 2919:		   }
        -: 2920:	  } else {
        -: 2921:		   exp_terms[p_exp] = savepv(uniq_terms[j]); is_dummy[p_exp] = false; p_exp++;
        -: 2922:	  }
        -: 2923:	}
        -: 2924:	p = p_exp;
        -: 2925:
        -: 2926:	Newx(X, n * p, double); Newx(Y, n, double);
        -: 2927:	Newx(valid_row_names, n, char*);
        -: 2928:
        -: 2929:	// --- Listwise Deletion ---
        -: 2930:	for (size_t i = 0; i < n; i++) {
        -: 2931:		double y_val = evaluate_term(data_hoa, row_hashes, i, lhs);
        -: 2932:		if (isnan(y_val)) { Safefree(row_names[i]); continue; }
        -: 2933:
        -: 2934:		bool row_ok = true;
        -: 2935:		double *restrict row_x = (double*)safemalloc(p * sizeof(double));
        -: 2936:		for (size_t j = 0; j < p; j++) {
        -: 2937:			if (strcmp(exp_terms[j], "Intercept") == 0) {
        -: 2938:				 row_x[j] = 1.0;
        -: 2939:			} else if (is_dummy[j]) {
        -: 2940:				 char* str_val = get_data_string_alloc(data_hoa, row_hashes, i, dummy_base[j]);
        -: 2941:				 if (str_val) {
        -: 2942:				     row_x[j] = (strcmp(str_val, dummy_level[j]) == 0) ? 1.0 : 0.0;
        -: 2943:				     Safefree(str_val);
        -: 2944:				 } else { row_ok = false; break; }
        -: 2945:			} else {
        -: 2946:				 row_x[j] = evaluate_term(data_hoa, row_hashes, i, exp_terms[j]);
        -: 2947:				 if (isnan(row_x[j])) { row_ok = false; break; }
        -: 2948:			}
        -: 2949:		}
        -: 2950:		if (!row_ok) { Safefree(row_names[i]); Safefree(row_x); continue; }
        -: 2951:		Y[valid_n] = y_val;
        -: 2952:		for (size_t j = 0; j < p; j++) X[valid_n * p + j] = row_x[j];
        -: 2953:		valid_row_names[valid_n] = row_names[i];
        -: 2954:		valid_n++;
        -: 2955:		Safefree(row_x);
        -: 2956:	}
        -: 2957:	Safefree(row_names); 
        -: 2958:	if (valid_n <= p) {
        -: 2959:	  Safefree(X); Safefree(Y); Safefree(valid_row_names); if (row_hashes) Safefree(row_hashes);
        -: 2960:	  croak("glm: 0 degrees of freedom (too many NAs or parameters > observations)");
        -: 2961:	}
        -: 2962:	// --- R glm.fit IRLS Implementation ---
        -: 2963:	mu = (double*)safemalloc(valid_n * sizeof(double)); eta = (double*)safemalloc(valid_n * sizeof(double));
        -: 2964:	W = (double*)safemalloc(valid_n * sizeof(double)); Z = (double*)safemalloc(valid_n * sizeof(double));
        -: 2965:	beta = (double*)safemalloc(p * sizeof(double)); beta_old = (double*)safemalloc(p * sizeof(double));
        -: 2966:	aliased = (bool*)safemalloc(p * sizeof(bool));
        -: 2967:	XtWX = (double*)safemalloc(p * p * sizeof(double)); XtWZ = (double*)safemalloc(p * sizeof(double));
        -: 2968:	for (i = 0; i < p; i++) { beta[i] = 0.0; beta_old[i] = 0.0; }
        -: 2969:	// Initialize (mustart / etastart equivalent)
        -: 2970:	double sum_y = 0.0;
        -: 2971:	for (i = 0; i < valid_n; i++) sum_y += Y[i];
        -: 2972:	double mean_y = sum_y / valid_n;
        -: 2973:	for (i = 0; i < valid_n; i++) {
        -: 2974:	  if (is_binomial) {
        -: 2975:		   if (Y[i] < 0.0 || Y[i] > 1.0) croak("glm: binomial family requires response between 0 and 1");
        -: 2976:		   mu[i] = (Y[i] + 0.5) / 2.0; 
        -: 2977:		   eta[i] = log(mu[i] / (1.0 - mu[i]));
        -: 2978:		   double dev = 0.0;
        -: 2979:		   if (Y[i] == 0.0)      dev = -2.0 * log(1.0 - mu[i]);
        -: 2980:		   else if (Y[i] == 1.0) dev = -2.0 * log(mu[i]);
        -: 2981:		   else dev = 2.0 * (Y[i] * log(Y[i] / mu[i]) + (1.0 - Y[i]) * log((1.0 - Y[i]) / (1.0 - mu[i])));
        -: 2982:		   deviance_old += dev;
        -: 2983:	  } else { 
        -: 2984:		   mu[i] = mean_y; // R gaussian init
        -: 2985:		   eta[i] = mu[i]; 
        -: 2986:	  }
        -: 2987:	}
        -: 2988:	// IRLS Loop
        -: 2989:	for (iter = 1; iter <= max_iter; iter++) {
        -: 2990:		for (i = 0; i < valid_n; i++) {
        -: 2991:			if (is_binomial) {
        -: 2992:				 double varmu = mu[i] * (1.0 - mu[i]);
        -: 2993:				 double mu_eta = varmu; // Link derivative for logit
        -: 2994:				 if (varmu < 1e-10) varmu = 1e-10;
        -: 2995:				 Z[i] = eta[i] + (Y[i] - mu[i]) / mu_eta;
        -: 2996:				 W[i] = (mu_eta * mu_eta) / varmu; 
        -: 2997:			} else { 
        -: 2998:				 W[i] = 1.0; 
        -: 2999:				 Z[i] = Y[i]; 
        -: 3000:			}
        -: 3001:		}
        -: 3002:		// Formulate XtWX and XtWZ
        -: 3003:		for (i = 0; i < p; i++) { XtWZ[i] = 0.0; for (size_t j = 0; j < p; j++) XtWX[i * p + j] = 0.0; }
        -: 3004:		for (size_t k = 0; k < valid_n; k++) {
        -: 3005:			double w = W[k], z = Z[k];
        -: 3006:			for (i = 0; i < p; i++) {
        -: 3007:				 XtWZ[i] += X[k * p + i] * w * z;
        -: 3008:				 double xw = X[k * p + i] * w;
        -: 3009:				 for (size_t j = 0; j < p; j++) XtWX[i * p + j] += xw * X[k * p + j];
        -: 3010:			}
        -: 3011:		}
        -: 3012:		final_rank = sweep_matrix_ols(XtWX, p, aliased);
        -: 3013:		for (i = 0; i < p; i++) {
        -: 3014:			if (aliased[i]) { beta[i] = NAN; } else {
        -: 3015:				 double sum = 0.0;
        -: 3016:				 for (size_t j = 0; j < p; j++) if (!aliased[j]) sum += XtWX[i * p + j] * XtWZ[j];
        -: 3017:				 beta[i] = sum;
        -: 3018:			}
        -: 3019:		}
        -: 3020:		// Calculate updated ETA, MU, and Deviance (with Step-Halving)
        -: 3021:		boundary = false;
        -: 3022:		for (unsigned short int half = 0; half < 10; half++) {
        -: 3023:			deviance_new = 0.0;
        -: 3024:			for (i = 0; i < valid_n; i++) {
        -: 3025:				 double linear_pred = 0.0;
        -: 3026:				 for (size_t j = 0; j < p; j++) if (!aliased[j]) linear_pred += X[i * p + j] * beta[j];
        -: 3027:				 eta[i] = linear_pred;
        -: 3028:				 if (is_binomial) {
        -: 3029:				     mu[i] = 1.0 / (1.0 + exp(-eta[i]));
        -: 3030:				     // Boundary enforcement
        -: 3031:				     if (mu[i] < 10 * DBL_EPSILON) mu[i] = 10 * DBL_EPSILON;
        -: 3032:				     if (mu[i] > 1.0 - 10 * DBL_EPSILON) mu[i] = 1.0 - 10 * DBL_EPSILON;
        -: 3033:
        -: 3034:				     double dev = 0.0;
        -: 3035:				     if (Y[i] == 0.0)      dev = -2.0 * log(1.0 - mu[i]);
        -: 3036:				     else if (Y[i] == 1.0) dev = -2.0 * log(mu[i]);
        -: 3037:				     else dev = 2.0 * (Y[i] * log(Y[i] / mu[i]) + (1.0 - Y[i]) * log((1.0 - Y[i]) / (1.0 - mu[i])));
        -: 3038:				     deviance_new += dev;
        -: 3039:				 } else {
        -: 3040:				     mu[i] = eta[i];
        -: 3041:				     double res = Y[i] - mu[i];
        -: 3042:				     deviance_new += res * res;
        -: 3043:				 }
        -: 3044:			}
        -: 3045:			// Step halving divergence check
        -: 3046:			if (!is_binomial || deviance_new <= deviance_old + 1e-7 || !isfinite(deviance_new)) {
        -: 3047:				 continue; 
        -: 3048:			}
        -: 3049:
        -: 3050:			boundary = true;
        -: 3051:			for (size_t j = 0; j < p; j++) beta[j] = (beta[j] + beta_old[j]) / 2.0;
        -: 3052:		}
        -: 3053:		// Convergence Check
        -: 3054:		if (fabs(deviance_new - deviance_old) / (0.1 + fabs(deviance_new)) < epsilon) { 
        -: 3055:			converged = true; break; 
        -: 3056:		}
        -: 3057:		deviance_old = deviance_new;
        -: 3058:		for (size_t j = 0; j < p; j++) beta_old[j] = beta[j];
        -: 3059:	}
        -: 3060:	// Final accurate calculation of W for standard errors
        -: 3061:	for (i = 0; i < p; i++) { for (size_t j = 0; j < p; j++) XtWX[i * p + j] = 0.0; }
        -: 3062:	for (size_t k = 0; k < valid_n; k++) {
        -: 3063:	  double w = is_binomial ? (mu[k] * (1.0 - mu[k])) : 1.0;
        -: 3064:	  if (w < 1e-10) w = 1e-10;
        -: 3065:	  for (i = 0; i < p; i++) {
        -: 3066:		   double xw = X[k * p + i] * w;
        -: 3067:		   for (size_t j = 0; j < p; j++) XtWX[i * p + j] += xw * X[k * p + j];
        -: 3068:	  }
        -: 3069:	}
        -: 3070:	final_rank = sweep_matrix_ols(XtWX, p, aliased);
        -: 3071:	// --- Null Deviance Calculation ---
        -: 3072:	double wtdmu = mean_y; // Since weights are 1.0 initially
        -: 3073:	for (i = 0; i < valid_n; i++) {
        -: 3074:	  if (is_binomial) {
        -: 3075:		   if (Y[i] == 0.0)      null_dev += -2.0 * log(1.0 - wtdmu);
        -: 3076:		   else if (Y[i] == 1.0) null_dev += -2.0 * log(wtdmu);
        -: 3077:		   else null_dev += 2.0 * (Y[i] * log(Y[i] / wtdmu) + (1.0 - Y[i]) * log((1.0 - Y[i]) / (1.0 - wtdmu)));
        -: 3078:	  } else {
        -: 3079:		   double diff = Y[i] - wtdmu;
        -: 3080:		   null_dev += diff * diff;
        -: 3081:	  }
        -: 3082:	}
        -: 3083:	// --- AIC Calculation ---
        -: 3084:	if (is_gaussian) {
        -: 3085:	  double n_f = (double)valid_n;
        -: 3086:	  aic = n_f * (log(2.0 * M_PI) + 1.0 + log(deviance_new / n_f)) + 2.0 * (final_rank + 1.0);
        -: 3087:	} else if (is_binomial) { 
        -: 3088:	  aic = deviance_new + 2.0 * final_rank; 
        -: 3089:	}
        -: 3090:	// --- Return Structures ---
        -: 3091:	res_hv = newHV(); coef_hv = newHV(); fitted_hv = newHV(); resid_hv = newHV();
        -: 3092:	df_res = valid_n - final_rank;
        -: 3093:	dispersion = is_binomial ? 1.0 : ((df_res > 0) ? (deviance_new / df_res) : NAN);
        -: 3094:	for (size_t i = 0; i < valid_n; i++) {
        -: 3095:		double res = Y[i] - mu[i];
        -: 3096:		if (is_binomial) {
        -: 3097:			// Deviance residuals for binomial
        -: 3098:			double d_res = 0.0;
        -: 3099:			if (Y[i] == 0.0)      d_res = sqrt(-2.0 * log(1.0 - mu[i]));
        -: 3100:			else if (Y[i] == 1.0) d_res = sqrt(-2.0 * log(mu[i]));
        -: 3101:			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]))));
        -: 3102:			res = (Y[i] > mu[i]) ? d_res : -d_res;
        -: 3103:		}
        -: 3104:		hv_store(fitted_hv, valid_row_names[i], strlen(valid_row_names[i]), newSVnv(mu[i]), 0);
        -: 3105:		hv_store(resid_hv,  valid_row_names[i], strlen(valid_row_names[i]), newSVnv(res), 0);
        -: 3106:		Safefree(valid_row_names[i]);
        -: 3107:	}
        -: 3108:	Safefree(valid_row_names);
        -: 3109:
        -: 3110:	summary_hv = newHV(); terms_av = newAV();
        -: 3111:	for (size_t j = 0; j < p; j++) {
        -: 3112:		hv_store(coef_hv, exp_terms[j], strlen(exp_terms[j]), newSVnv(beta[j]), 0);
        -: 3113:		av_push(terms_av, newSVpv(exp_terms[j], 0));
        -: 3114:
        -: 3115:		HV *restrict row_hv = newHV();
        -: 3116:		if (aliased[j]) {
        -: 3117:			hv_store(row_hv, "Estimate",   8, newSVpv("NaN", 0), 0);
        -: 3118:			hv_store(row_hv, "Std. Error", 10, newSVpv("NaN", 0), 0);
        -: 3119:			hv_store(row_hv, is_binomial ? "z value" : "t value", 7, newSVpv("NaN", 0), 0);
        -: 3120:			hv_store(row_hv, is_binomial ? "Pr(>|z|)" : "Pr(>|t|)", 8, newSVpv("NaN", 0), 0);
        -: 3121:		} else {
        -: 3122:			double se = sqrt(dispersion * XtWX[j * p + j]);
        -: 3123:			double val_stat = beta[j] / se;
        -: 3124:			double p_val = is_binomial ? 2.0 * (1.0 - approx_pnorm(fabs(val_stat))) : get_t_pvalue(val_stat, df_res, "two.sided");
        -: 3125:
        -: 3126:			hv_store(row_hv, "Estimate",   8, newSVnv(beta[j]), 0);
        -: 3127:			hv_store(row_hv, "Std. Error", 10, newSVnv(se), 0);
        -: 3128:			hv_store(row_hv, is_binomial ? "z value" : "t value", 7, newSVnv(val_stat), 0);
        -: 3129:			hv_store(row_hv, is_binomial ? "Pr(>|z|)" : "Pr(>|t|)", 8, newSVnv(p_val), 0);
        -: 3130:		}
        -: 3131:		hv_store(summary_hv, exp_terms[j], strlen(exp_terms[j]), newRV_noinc((SV*)row_hv), 0);
        -: 3132:	}
        -: 3133:
        -: 3134:	hv_store(res_hv, "aic",            3, newSVnv(aic), 0);
        -: 3135:	hv_store(res_hv, "coefficients",  12, newRV_noinc((SV*)coef_hv), 0);
        -: 3136:	hv_store(res_hv, "converged",      9, newSVuv(converged ? 1 : 0), 0);
        -: 3137:	hv_store(res_hv, "boundary",       8, newSVuv(boundary ? 1 : 0), 0);
        -: 3138:	hv_store(res_hv, "deviance",       8, newSVnv(deviance_new), 0);
        -: 3139:	hv_store(res_hv, "deviance.resid", 14, newRV_noinc((SV*)resid_hv), 0);
        -: 3140:	hv_store(res_hv, "df.null",        7, newSVuv(valid_n - has_intercept), 0);
        -: 3141:	hv_store(res_hv, "df.residual",   11, newSVuv(df_res), 0);
        -: 3142:	hv_store(res_hv, "family",         6, newSVpv(family_str, 0), 0);
        -: 3143:	hv_store(res_hv, "fitted.values", 13, newRV_noinc((SV*)fitted_hv), 0);
        -: 3144:	hv_store(res_hv, "iter",           4, newSVuv(iter > max_iter ? max_iter : iter), 0);
        -: 3145:	hv_store(res_hv, "null.deviance", 13, newSVnv(null_dev), 0);
        -: 3146:	hv_store(res_hv, "rank",           4, newSVuv(final_rank), 0);
        -: 3147:	hv_store(res_hv, "summary",        7, newRV_noinc((SV*)summary_hv), 0);
       21: 3148:	hv_store(res_hv, "terms",          5, newRV_noinc((SV*)terms_av), 0);
call    0 returned 21
       21: 3149:
        -: 3150:	// --- Cleanup ---
       21: 3151:	for (i = 0; i < num_terms; i++) Safefree(terms[i]);
        -: 3152:	Safefree(terms);
        -: 3153:	for (i = 0; i < num_uniq; i++) Safefree(uniq_terms[i]);
        -: 3154:	Safefree(uniq_terms);
        -: 3155:	for (size_t j = 0; j < p_exp; j++) {
function XS_Stats__LikeR_cor_test called 21 returned 100% blocks executed 76%
       21: 3156:		Safefree(exp_terms[j]);
        -: 3157:		if (is_dummy[j]) { Safefree(dummy_base[j]); Safefree(dummy_level[j]); }
       21: 3158:	}
       21: 3158-block  0
call    0 returned 21
branch  1 taken 21 (fallthrough)
branch  2 taken 0
        -: 3159:	Safefree(exp_terms); Safefree(is_dummy); Safefree(dummy_base); Safefree(dummy_level);
        -: 3160:
        -: 3161:	Safefree(mu); Safefree(eta); Safefree(Z); Safefree(W);
        -: 3162:	Safefree(beta); Safefree(beta_old); Safefree(aliased);
        -: 3163:	Safefree(XtWX); Safefree(XtWZ); Safefree(X); Safefree(Y);
        -: 3164:	if (row_hashes) Safefree(row_hashes);
        -: 3165:
        -: 3166:	RETVAL = newRV_noinc((SV*)res_hv);
        -: 3167:}
        -: 3168:#line 3169 "LikeR.c"
        -: 3169:	RETVAL = sv_2mortal(RETVAL);
        -: 3170:	ST(0) = RETVAL;
        -: 3171:    }
        -: 3172:    XSRETURN(1);
        -: 3173:}
        -: 3174:
        -: 3175:
        -: 3176:XS_EUPXS(XS_Stats__LikeR_cor_test); /* prototype to pass -Wmissing-prototypes */
        -: 3177:XS_EUPXS(XS_Stats__LikeR_cor_test)
        -: 3178:{
        -: 3179:    dVAR; dXSARGS;
        -: 3180:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3181:    PERL_UNUSED_VAR(items); /* -W */
        -: 3182:    {
        -: 3183:	SV *	RETVAL;
        -: 3184:#line 2907 "LikeR.xs"
        -: 3185:{
        -: 3186:	if (items < 2 || items % 2 != 0)
        -: 3187:		croak("Usage: cor_test(\\@x, \\@y, method => 'pearson', ...)");
        -: 3188:
        -: 3189:	SV *restrict x_ref = ST(0), *restrict y_ref = ST(1);
        -: 3190:
        -: 3191:	const char *restrict alternative = "two.sided";
        -: 3192:	const char *restrict method = "pearson";
        -: 3193:	SV *restrict exact_sv = NULL;
        -: 3194:	double conf_level = 0.95;
        -: 3195:	bool continuity = 0;
        -: 3196:
        -: 3197:	/* Parse named arguments from the flat stack starting at index 2 */
        -: 3198:	for (unsigned short int i = 2; i < items; i += 2) {
        -: 3199:	  const char *restrict key = SvPV_nolen(ST(i));
        -: 3200:	  SV *restrict val = ST(i + 1);
        -: 3201:
        -: 3202:	  if      (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 3203:	  else if (strEQ(key, "method"))      method = SvPV_nolen(val);
        -: 3204:	  else if (strEQ(key, "exact"))       exact_sv = val;
        -: 3205:	  else if (strEQ(key, "conf.level") || strEQ(key, "conf_level")) conf_level = SvNV(val);
        -: 3206:	  else if (strEQ(key, "continuity"))  continuity = SvTRUE(val);
        -: 3207:	  else croak("cor_test: unknown argument '%s'", key);
        -: 3208:	}
        -: 3209:
        -: 3210:	AV *restrict x_av, *restrict y_av;
        -: 3211:	double *restrict x, *restrict y;
        -: 3212:	double estimate = 0, p_value = 0, statistic = 0, df = 0, ci_lower = 0, ci_upper = 0;
        -: 3213:
        -: 3214:	bool is_pearson  = (strcmp(method, "pearson") == 0);
        -: 3215:	bool is_kendall  = (strcmp(method, "kendall") == 0);
        -: 3216:	bool is_spearman = (strcmp(method, "spearman") == 0);
        -: 3217:	HV *restrict rhv;
        -: 3218:
        -: 3219:	if (!SvOK(x_ref) || !SvROK(x_ref) || SvTYPE(SvRV(x_ref)) != SVt_PVAV ||
        -: 3220:	    !SvOK(y_ref) || !SvROK(y_ref) || SvTYPE(SvRV(y_ref)) != SVt_PVAV) {
        -: 3221:	  croak("cor_test: x and y must be array references");
        -: 3222:	}
        -: 3223:
        -: 3224:	x_av = (AV*)SvRV(x_ref);
        -: 3225:	y_av = (AV*)SvRV(y_ref);
        -: 3226:
        -: 3227:	size_t n_raw = av_len(x_av) + 1;
        -: 3228:	if (n_raw != av_len(y_av) + 1) croak("incompatible dimensions");
        -: 3229:
        -: 3230:	x = safemalloc(n_raw * sizeof(double));
        -: 3231:	y = safemalloc(n_raw * sizeof(double));
        -: 3232:
        -: 3233:	size_t n = 0; /* Final count of pairwise complete observations */
        -: 3234:	for (size_t i = 0; i < n_raw; i++) {
        -: 3235:	  SV **restrict x_val = av_fetch(x_av, i, 0);
        -: 3236:	  SV **restrict y_val = av_fetch(y_av, i, 0);
        -: 3237:
        -: 3238:	  double xv = (x_val && SvOK(*x_val) && looks_like_number(*x_val)) ? SvNV(*x_val) : NAN;
        -: 3239:	  double yv = (y_val && SvOK(*y_val) && looks_like_number(*y_val)) ? SvNV(*y_val) : NAN;
        -: 3240:
        -: 3241:	  /* Pairwise complete observations (skips NAs seamlessly like R) */
        -: 3242:	  if (!isnan(xv) && !isnan(yv)) {
        -: 3243:	      x[n] = xv;
        -: 3244:	      y[n] = yv;
        -: 3245:	      n++;
        -: 3246:	  }
        -: 3247:	}
        -: 3248:
        -: 3249:	if (n < 3) {
        -: 3250:	  Safefree(x); 
        -: 3251:	  Safefree(y);
        -: 3252:	  croak("not enough finite observations");
        -: 3253:	}
        -: 3254:
        -: 3255:	if (is_pearson) {
        -: 3256:	  // Welford's Method for Pearson Correlation
        -: 3257:	  double mean_x = 0.0, mean_y = 0.0, M2_x = 0.0, M2_y = 0.0, cov = 0.0;
        -: 3258:	  for (size_t i = 0; i < n; i++) {
        -: 3259:		   double dx = x[i] - mean_x;
        -: 3260:		   mean_x += dx / (i + 1);
        -: 3261:		   double dy = y[i] - mean_y;
        -: 3262:		   mean_y += dy / (i + 1);
        -: 3263:		   M2_x += dx * (x[i] - mean_x);
        -: 3264:		   M2_y += dy * (y[i] - mean_y);
        -: 3265:		   cov  += dx * (y[i] - mean_y);
        -: 3266:	  }
        -: 3267:	  estimate = (M2_x > 0.0 && M2_y > 0.0) ? cov / sqrt(M2_x * M2_y) : 0.0;
        -: 3268:	  df = n - 2;
        -: 3269:	  statistic = estimate * sqrt(df / (1.0 - estimate * estimate));
        -: 3270:
        -: 3271:	  // Confidence interval using Fisher's Z transform
        -: 3272:	  double z = 0.5 * log((1.0 + estimate) / (1.0 - estimate));
        -: 3273:	  double se = 1.0 / sqrt(n - 3);
        -: 3274:	  double alpha = 1.0 - conf_level;
        -: 3275:	  double q = inverse_normal_cdf(1.0 - alpha/2.0);
        -: 3276:	  ci_lower = tanh(z - q * se);
        -: 3277:	  ci_upper = tanh(z + q * se);
        -: 3278:
        -: 3279:	  // HIGH-PRECISION P-VALUE USING INCOMPLETE BETA
        -: 3280:	  p_value = get_t_pvalue(statistic, df, alternative);
        -: 3281:	} else if (is_kendall) {
        -: 3282:	  int c = 0, d = 0, tie_x = 0, tie_y = 0;
        -: 3283:	  for (size_t i = 0; i < n - 1; i++) {
        -: 3284:		   for (size_t j = i + 1; j < n; j++) {
        -: 3285:		       double sign_x = (x[i] - x[j] > 0) - (x[i] - x[j] < 0);
        -: 3286:		       double sign_y = (y[i] - y[j] > 0) - (y[i] - y[j] < 0);
        -: 3287:
        -: 3288:		       if (sign_x == 0 && sign_y == 0) { /* Joint tie, ignore */ }
        -: 3289:		       else if (sign_x == 0) tie_x++;
        -: 3290:		       else if (sign_y == 0) tie_y++;
        -: 3291:		       else if (sign_x * sign_y > 0) c++;
        -: 3292:		       else d++;
        -: 3293:		   }
        -: 3294:	  }
        -: 3295:	  double denom = sqrt((double)(c + d + tie_x) * (double)(c + d + tie_y));
        -: 3296:	  estimate = (denom == 0.0) ? (0.0/0.0) : (double)(c - d) / denom;
        -: 3297:
        -: 3298:	  bool has_ties = (tie_x > 0 || tie_y > 0);
        -: 3299:	  bool do_exact;
        -: 3300:
        -: 3301:	  /* Mirror R: exact defaults to TRUE if N < 50 and NO ties */
        -: 3302:	  if (!exact_sv || !SvOK(exact_sv)) {
        -: 3303:		   do_exact = (n < 50) && !has_ties;
        -: 3304:	  } else {
        -: 3305:		   do_exact = SvTRUE(exact_sv) ? 1 : 0;
        -: 3306:	  }
        -: 3307:	  // If forced exact but ties exist, R overrides and falls back to approximation anyway
        -: 3308:	  if (do_exact && has_ties) do_exact = 0;
        -: 3309:
        -: 3310:	  if (do_exact) {
        -: 3311:		   double S_stat = c - d;
        -: 3312:		   statistic = c;
        -: 3313:		   p_value = kendall_exact_pvalue(n, S_stat, alternative);
        -: 3314:	  } else {
        -: 3315:		   // Normal approximation for large N or ties
        -: 3316:		   double var_S = n * (n - 1) * (2.0 * n + 5.0) / 18.0;
        -: 3317:		   double S = c - d;
        -: 3318:		   if (continuity) S -= (S > 0 ? 1 : -1);
        -: 3319:		   statistic = S / sqrt(var_S);
        -: 3320:
        -: 3321:		   if (strcmp(alternative, "two.sided") == 0) {
        -: 3322:		       p_value = 2.0 * (1.0 - approx_pnorm(fabs(statistic)));
        -: 3323:		   } else if (strcmp(alternative, "less") == 0) {
        -: 3324:		       p_value = approx_pnorm(statistic);
        -: 3325:		   } else {
        -: 3326:		       p_value = 1.0 - approx_pnorm(statistic);
        -: 3327:		   }
        -: 3328:	  }
        -: 3329:	} else if (is_spearman) {
        -: 3330:	  double *restrict rank_x = safemalloc(n * sizeof(double));
        -: 3331:	  double *restrict rank_y = safemalloc(n * sizeof(double));
        -: 3332:	  compute_ranks(x, rank_x, n);
        -: 3333:	  compute_ranks(y, rank_y, n);
        -: 3334:
        -: 3335:	  // Spearman rho = Pearson r of the ranks (Welford's Method)
        -: 3336:	  double mean_x = 0.0, mean_y = 0.0, M2_x = 0.0, M2_y = 0.0, cov = 0.0;
        -: 3337:	  for (size_t i = 0; i < n; i++) {
        -: 3338:		   double dx = rank_x[i] - mean_x;
        -: 3339:		   mean_x += dx / (i + 1);
        -: 3340:		   double dy = rank_y[i] - mean_y;
        -: 3341:		   mean_y += dy / (i + 1);
        -: 3342:		   M2_x += dx * (rank_x[i] - mean_x);
        -: 3343:		   M2_y += dy * (rank_y[i] - mean_y);
        -: 3344:		   cov  += dx * (rank_y[i] - mean_y);
        -: 3345:	  }
        -: 3346:	  estimate = (M2_x > 0.0 && M2_y > 0.0) ? cov / sqrt(M2_x * M2_y) : 0.0;
        -: 3347:
        -: 3348:	  // S = sum of squared rank differences (R's reported statistic)
        -: 3349:	  double S_stat = 0.0;
        -: 3350:	  for (size_t i = 0; i < n; i++) {
        -: 3351:		   double diff = rank_x[i] - rank_y[i];
        -: 3352:		   S_stat += diff * diff;
        -: 3353:	  }
        -: 3354:
        -: 3355:	  // Ties produce fractional (averaged) ranks — detect them
        -: 3356:	  bool has_ties = 0, do_exact;
        -: 3357:	  for (size_t i = 0; i < n; i++) {
        -: 3358:		   if (rank_x[i] != floor(rank_x[i]) || rank_y[i] != floor(rank_y[i])) {
        -: 3359:		       has_ties = 1;
        -: 3360:		       break;
        -: 3361:		   }
        -: 3362:	  }
        -: 3363:	  if (!exact_sv || !SvOK(exact_sv)) {
        -: 3364:		   do_exact = (n < 10) && !has_ties;
        -: 3365:	  } else {
        -: 3366:		   do_exact = SvTRUE(exact_sv) ? 1 : 0;
        -: 3367:	  }
        -: 3368:
        -: 3369:	  if (do_exact) {
        -: 3370:		   statistic = S_stat;
        -: 3371:		   p_value   = spearman_exact_pvalue(S_stat, n, alternative);
        -: 3372:	  } else {
        -: 3373:		   double r = estimate;
        -: 3374:		   if (continuity)
        -: 3375:		       r *= (1.0 - 1.0 / (2.0 * (n - 1)));
        -: 3376:		   statistic = r * sqrt((n - 2.0) / (1.0 - r * r));
        -: 3377:		   p_value = get_t_pvalue(statistic, (double)(n - 2), alternative);
        -: 3378:	  }
        -: 3379:	  Safefree(rank_x); Safefree(rank_y);
        -: 3380:	} else {
       21: 3381:	  Safefree(x); Safefree(y);
call    0 returned 21
       21: 3382:	  croak("Unknown method");
        -: 3383:	}
       21: 3384:	Safefree(x); Safefree(y);
        -: 3385:	rhv = newHV();
        -: 3386:	hv_stores(rhv, "estimate", newSVnv(estimate));
        -: 3387:	hv_stores(rhv, "p.value", newSVnv(p_value));
        -: 3388:	hv_stores(rhv, "statistic", newSVnv(statistic));
function XS_Stats__LikeR_shapiro_test called 6 returned 100% blocks executed 81%
        6: 3389:	hv_stores(rhv, "method", newSVpv(method, 0));
        -: 3390:	hv_stores(rhv, "alternative", newSVpv(alternative, 0));
        6: 3391:	if (is_pearson) {
        6: 3391-block  0
call    0 returned 6
        6: 3392:	  hv_stores(rhv, "parameter", newSVnv(df));
branch  0 taken 0 (fallthrough)
branch  1 taken 6
    #####: 3393:	  AV *restrict ci_av = newAV();
    %%%%%: 3393-block  0
call    0 never executed
        -: 3394:	  av_push(ci_av, newSVnv(ci_lower));
        6: 3395:	  av_push(ci_av, newSVnv(ci_upper));
        -: 3396:	  hv_stores(rhv, "conf.int", newRV_noinc((SV*)ci_av));
        6: 3397:	}
        6: 3397-block  0
branch  0 taken 6 (fallthrough)
branch  1 taken 0
        -: 3398:
        -: 3399:	RETVAL = newRV_noinc((SV*)rhv);
        -: 3400:}
        -: 3401:#line 3402 "LikeR.c"
        -: 3402:	RETVAL = sv_2mortal(RETVAL);
        -: 3403:	ST(0) = RETVAL;
        -: 3404:    }
        -: 3405:    XSRETURN(1);
        -: 3406:}
        -: 3407:
        -: 3408:
        -: 3409:XS_EUPXS(XS_Stats__LikeR_shapiro_test); /* prototype to pass -Wmissing-prototypes */
        -: 3410:XS_EUPXS(XS_Stats__LikeR_shapiro_test)
        -: 3411:{
        -: 3412:    dVAR; dXSARGS;
        -: 3413:    if (items != 1)
        -: 3414:       croak_xs_usage(cv,  "data");
        -: 3415:    PERL_UNUSED_VAR(ax); /* -Wall */
        -: 3416:    SP -= items;
        -: 3417:    {
        -: 3418:	SV *	data = ST(0)
        -: 3419:;
        -: 3420:#line 3129 "LikeR.xs"
        -: 3421:	AV *restrict av;
        -: 3422:	HV *restrict ret_hash;
        -: 3423:	size_t n_raw, n = 0;
        -: 3424:	double *restrict x, w = 0.0, p_val = 0.0, mean = 0.0, ssq = 0.0;
        -: 3425:#line 3426 "LikeR.c"
        -: 3426:#line 3134 "LikeR.xs"
        -: 3427:	if (!SvROK(data) || SvTYPE(SvRV(data)) != SVt_PVAV) {
        -: 3428:	  croak("Expected an array reference");
        -: 3429:	}
        -: 3430:
        -: 3431:	av = (AV *)SvRV(data);
        -: 3432:	n_raw = av_len(av) + 1;
        -: 3433:
        -: 3434:	Newx(x, n_raw, double);
        -: 3435:
        -: 3436:	// Extract variables and calculate mean (skipping undefined/NaN values)
        -: 3437:	for (size_t i = 0; i < n_raw; i++) {
        -: 3438:	  SV **restrict elem = av_fetch(av, i, 0);
        -: 3439:	  if (elem && SvOK(*elem)) {
        -: 3440:		   double val = SvNV(*elem);
        -: 3441:		   if (!isnan(val)) {
        -: 3442:		       x[n] = val;
        -: 3443:		       mean += val;
        -: 3444:		       n++;
        -: 3445:		   }
        -: 3446:	  }
        -: 3447:	}
        -: 3448:
        -: 3449:	if (n < 3 || n > 5000) {
        -: 3450:	  Safefree(x);
        -: 3451:	  croak("Sample size must be between 3 and 5000 (R's limit)");
        -: 3452:	}
        -: 3453:
        -: 3454:	mean /= n;
        -: 3455:	// Calculate Sum of Squares */
        -: 3456:	for (size_t i = 0; i < n; i++) {
        -: 3457:	  ssq += (x[i] - mean) * (x[i] - mean);
        -: 3458:	}
        -: 3459:	if (ssq == 0.0) {
        -: 3460:	  Safefree(x);
        -: 3461:	  croak("Data is perfectly constant; cannot compute Shapiro-Wilk test");
        -: 3462:	}
        -: 3463:	qsort(x, n, sizeof(double), compare_doubles);
        -: 3464:
        -: 3465:	// --- Core AS R94 Algorithm: Weights and Statistic W ---
        -: 3466:	if (n == 3) {
        -: 3467:	  double a_val = 0.7071067811865475; /* sqrt(1/2) */
        -: 3468:	  double b_val = a_val * (x[2] - x[0]);
        -: 3469:	  w = (b_val * b_val) / ssq;
        -: 3470:	  if (w < 0.75) w = 0.75; 
        -: 3471:	  // Exact P-value for n=3
        -: 3472:	  p_val = 1.90985931710274 * (asin(sqrt(w)) - 1.04719755119660);
        -: 3473:	} else {
        -: 3474:	  double *restrict m, *restrict a;
        -: 3475:	  double sum_m2 = 0.0, b_val = 0.0;
        -: 3476:	  Newx(m, n, double);
        -: 3477:	  Newx(a, n, double);
        -: 3478:	  for (size_t i = 0; i < n; i++) {
        -: 3479:		   m[i] = inverse_normal_cdf((i + 1.0 - 0.375) / (n + 0.25));
        -: 3480:		   sum_m2 += m[i] * m[i];
        -: 3481:	  }
        -: 3482:	  double u = 1.0 / sqrt((double)n);
        -: 3483:	  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);
        -: 3484:	  a[n-1] = a_n;
        -: 3485:	  a[0]   = -a_n;
        -: 3486:	  if (n == 4 || n == 5) {
        -: 3487:		   double eps = (sum_m2 - 2.0 * m[n-1]*m[n-1]) / (1.0 - 2.0 * a_n*a_n);
        -: 3488:		   for (unsigned int i = 1; i < n-1; i++) {
        -: 3489:		       a[i] = m[i] / sqrt(eps);
        -: 3490:		   }
        -: 3491:	  } else {
        -: 3492:		   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);
        -: 3493:		   a[n-2] = a_n1;
        -: 3494:		   a[1]   = -a_n1;
        -: 3495:		   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);
        -: 3496:		   for (unsigned int i = 2; i < n-2; i++) {
        -: 3497:		       a[i] = m[i] / sqrt(eps);
        -: 3498:		   }
        -: 3499:	  }
        -: 3500:	  for (size_t i = 0; i < n; i++) {
        -: 3501:		   b_val += a[i] * x[i];
        -: 3502:	  }
        -: 3503:	  w = (b_val * b_val) / ssq;
        -: 3504:	// --- AS R94 P-Value Calculation: High Precision Refinement ---
        -: 3505:	  /* NOTE: p_val is declared in PREINIT above;
        -: 3506:		* do NOT shadow it with a local 'double p_val' here or the result will never reach the caller.
        -: 3507:		*/
        -: 3508:	  double y = log(1.0 - w);
        -: 3509:	  double z;
        -: 3510:	  if (n <= 11) {
        -: 3511:		   // Royston's branch for 4 <= n <= 11 (AS R94, small-sample path).
        -: 3512:		   // gamma is the upper bound on y = log(1-W);
        -: 3513:		   // if y reaches gamma the p-value is essentially zero
        -: 3514:		   double nn = (double)n;
        -: 3515:		   double gamma = 0.459 * nn - 2.273;
        -: 3516:		   if (y >= gamma) {
        -: 3517:		       p_val = 1e-19;
        -: 3518:		   } else {
        -: 3519:		       // Horner-form polynomials in n for mu and log(sigma)
        -: 3520:		       double mu     = 0.544  + nn * (-0.39978  + nn * ( 0.025054  - nn * 0.0006714));
        -: 3521:		       double sig_val= 1.3822 + nn * (-0.77857  + nn * ( 0.062767  - nn * 0.0020322));
        -: 3522:		       double sigma  = exp(sig_val);
        -: 3523:		       z = (-log(gamma - y) - mu) / sigma;
        -: 3524:		       /* Upper-tail probability P(Z > z): small W → large z → small p-value.
        -: 3525:		       */
        -: 3526:		       p_val = 0.5 * erfc(z * M_SQRT1_2);
        -: 3527:		   }
        -: 3528:	  } else {
        -: 3529:		   // Royston's branch for n >= 12 (AS R94, large-sample path)
        -: 3530:		   double ln_n   = log((double)n);
        -: 3531:		   // Horner-form polynomials in log(n) for mu and log(sigma). */
        6: 3532:		   double mu     = -1.5861 + ln_n * (-0.31082 + ln_n * (-0.083751 + ln_n * 0.0038915));
        6: 3533:		   double sig_val= -0.4803 + ln_n * (-0.082676 + ln_n * 0.0030302);
        -: 3534:		   double sigma  = exp(sig_val);
        -: 3535:		   z = (y - mu) / sigma;
        -: 3536:		   p_val = 0.5 * erfc(z * M_SQRT1_2);
        -: 3537:	  }
        -: 3538:	  // Clamp the p-value
function XS_Stats__LikeR_min called 30 returned 90% blocks executed 94%
       30: 3539:	  if (p_val > 1.0) p_val = 1.0;
        -: 3540:	  if (p_val < 0.0) p_val = 0.0;
       30: 3541:
       30: 3541-block  0
call    0 returned 30
        -: 3542:	  Safefree(m); m = NULL;  Safefree(a); a = NULL;
        -: 3543:	}
        -: 3544:	Safefree(x); x = NULL;
        -: 3545:	ret_hash = newHV();
      30*: 3546:	hv_stores(ret_hash, "statistic", newSVnv(w));
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:	hv_stores(ret_hash, "W",         newSVnv(w));
        -: 3548:	hv_stores(ret_hash, "p_value",   newSVnv(p_val));
        -: 3549:	hv_stores(ret_hash, "p.value",   newSVnv(p_val));
        -: 3550:	EXTEND(SP, 1);
        -: 3551:	PUSHs(sv_2mortal(newRV_noinc((SV *)ret_hash)));
        -: 3552:#line 3553 "LikeR.c"
        -: 3553:	PUTBACK;
        -: 3554:	return;
        -: 3555:    }
        -: 3556:}
        -: 3557:
        -: 3558:
        -: 3559:XS_EUPXS(XS_Stats__LikeR_min); /* prototype to pass -Wmissing-prototypes */
        -: 3560:XS_EUPXS(XS_Stats__LikeR_min)
        -: 3561:{
        -: 3562:    dVAR; dXSARGS;
        -: 3563:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3564:    PERL_UNUSED_VAR(items); /* -W */
        -: 3565:    {
        -: 3566:	double	RETVAL;
        -: 3567:	dXSTARG;
        -: 3568:#line 3263 "LikeR.xs"
        -: 3569:		double min_val = 0.0;
        -: 3570:		size_t count = 0;
        -: 3571:		bool first = TRUE;
        -: 3572:#line 3573 "LikeR.c"
        -: 3573:#line 3267 "LikeR.xs"
        -: 3574:		for (unsigned short int i = 0; i < items; i++) {
        -: 3575:			SV* restrict arg = ST(i);
        -: 3576:			if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 3577:				AV* restrict av = (AV*)SvRV(arg);
        -: 3578:				size_t len = av_len(av) + 1;
        -: 3579:				for (size_t j = 0; j < len; j++) {
        -: 3580:				     SV** restrict tv = av_fetch(av, j, 0);
      27*: 3581:				     if (tv && SvOK(*tv)) {
       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:				         double val = SvNV(*tv);
        -: 3583:				         if (first || val < min_val) {
       27: 3584:				             min_val = val;
       27: 3584-block  0
        -: 3585:				             first = FALSE;
        -: 3586:				         }
        -: 3587:				         count++;
        -: 3588:				     } else {
function XS_Stats__LikeR_max called 33 returned 91% blocks executed 94%
       33: 3589:				         croak("min: undefined value at array ref index %zu (argument %d)", j, (int)i);
        -: 3590:				     }
       33: 3591:				 }
       33: 3591-block  0
call    0 returned 33
        -: 3592:			} else if (SvOK(arg)) {
        -: 3593:				 double val = SvNV(arg);
        -: 3594:				 if (first || val < min_val) {
        -: 3595:				     min_val = val;
      33*: 3596:				     first = FALSE;
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:				 }
        -: 3598:				 count++;
        -: 3599:			} else {
        -: 3600:				 croak("min: undefined value at argument index %d", (int)i);
        -: 3601:			}
        -: 3602:		}
        -: 3603:		if (count == 0) croak("min needs >= 1 numeric element");
        -: 3604:		RETVAL = min_val;
        -: 3605:#line 3606 "LikeR.c"
        -: 3606:	TARGn((double)RETVAL, 1);
        -: 3607:	ST(0) = TARG;
        -: 3608:    }
        -: 3609:    XSRETURN(1);
        -: 3610:}
        -: 3611:
        -: 3612:
        -: 3613:XS_EUPXS(XS_Stats__LikeR_max); /* prototype to pass -Wmissing-prototypes */
        -: 3614:XS_EUPXS(XS_Stats__LikeR_max)
        -: 3615:{
        -: 3616:    dVAR; dXSARGS;
        -: 3617:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3618:    PERL_UNUSED_VAR(items); /* -W */
        -: 3619:    {
        -: 3620:	double	RETVAL;
        -: 3621:	dXSTARG;
        -: 3622:#line 3304 "LikeR.xs"
        -: 3623:		double max_val = 0.0;
        -: 3624:		size_t count = 0;
        -: 3625:		bool first = TRUE;
        -: 3626:#line 3627 "LikeR.c"
        -: 3627:#line 3308 "LikeR.xs"
        -: 3628:		for (size_t i = 0; i < items; i++) {
        -: 3629:		   SV* restrict arg = ST(i);
        -: 3630:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
      30*: 3631:		       AV* restrict av = (AV*)SvRV(arg);
       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:		       size_t len = av_len(av) + 1;
        -: 3633:		       for (size_t j = 0; j < len; j++) {
       30: 3634:		           SV** restrict tv = av_fetch(av, j, 0);
       30: 3634-block  0
        -: 3635:		           if (tv && SvOK(*tv)) {
        -: 3636:		               double val = SvNV(*tv);
        -: 3637:		               if (first || val > max_val) {
        -: 3638:		                   max_val = val;
function XS_Stats__LikeR_runif called 12 returned 100% blocks executed 86%
       12: 3639:		                   first = FALSE;
        -: 3640:		               }
       12: 3641:		               count++;
       12: 3641-block  0
call    0 returned 12
branch  1 taken 0 (fallthrough)
branch  2 taken 12
        -: 3642:		           } else {
        -: 3643:		               croak("max: undefined value at array ref index %zu (argument %zu)", j, i);
        -: 3644:		           }
        -: 3645:		       }
        -: 3646:		   } else if (SvOK(arg)) {
        -: 3647:		       double val = SvNV(arg);
        -: 3648:		       if (first || val > max_val) {
        -: 3649:		           max_val = val;
        -: 3650:		           first = FALSE;
        -: 3651:		       }
        -: 3652:		       count++;
        -: 3653:		   } else {
        -: 3654:		       croak("max: undefined value at argument index %zu", i);
        -: 3655:		   }
        -: 3656:	  }
        -: 3657:	  if (count == 0) croak("max needs >= 1 numeric element");
        -: 3658:	  RETVAL = max_val;
        -: 3659:#line 3660 "LikeR.c"
        -: 3660:	TARGn((double)RETVAL, 1);
        -: 3661:	ST(0) = TARG;
        -: 3662:    }
        -: 3663:    XSRETURN(1);
        -: 3664:}
        -: 3665:
        -: 3666:
        -: 3667:XS_EUPXS(XS_Stats__LikeR_runif); /* prototype to pass -Wmissing-prototypes */
        -: 3668:XS_EUPXS(XS_Stats__LikeR_runif)
        -: 3669:{
        -: 3670:    dVAR; dXSARGS;
        -: 3671:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3672:    PERL_UNUSED_VAR(items); /* -W */
        -: 3673:    {
        -: 3674:	SV *	RETVAL;
        -: 3675:#line 3344 "LikeR.xs"
        -: 3676:{
        -: 3677:	size_t n = 0;
        -: 3678:	double min = 0.0, max = 1.0;
        -: 3679:
        -: 3680:	// Flags to track what has been assigned
        -: 3681:	bool n_set = 0, min_set = 0, max_set = 0;
        -: 3682:
        -: 3683:	unsigned int i = 0;
        -: 3684:
        -: 3685:	if (items == 0) {
        -: 3686:	  croak("Usage: runif(n, [min=0], [max=1]) or runif(n => $n, ...)");
        -: 3687:	}
        -: 3688:
        -: 3689:	while (i < items) {
        -: 3690:		// 1. Check if the current argument is a string key for a named parameter
        -: 3691:		if (i + 1 < items && SvPOK(ST(i))) {
        -: 3692:			char *restrict key = SvPV_nolen(ST(i));
        -: 3693:			if (strEQ(key, "n")) {
        -: 3694:				n = (size_t)SvUV(ST(i+1));
        -: 3695:				n_set = 1;
        -: 3696:				i += 2;
        -: 3697:				continue;
        -: 3698:			} else if (strEQ(key, "min")) {
        -: 3699:				min = SvNV(ST(i+1));
        -: 3700:				min_set = 1;
        -: 3701:				i += 2;
        -: 3702:				continue;
        -: 3703:			} else if (strEQ(key, "max")) {
        -: 3704:				max = SvNV(ST(i+1));
        -: 3705:				max_set = 1;
        -: 3706:				i += 2;
        -: 3707:				continue;
        -: 3708:			}
        -: 3709:		}
        -: 3710:
        -: 3711:		// 2. Fallback to positional parsing if it's not a recognized key
        -: 3712:		if (!n_set) {
        -: 3713:			n = (size_t)SvUV(ST(i));
        -: 3714:			n_set = 1;
        -: 3715:		} else if (!min_set) {
        -: 3716:			min = SvNV(ST(i));
        -: 3717:			min_set = 1;
        -: 3718:		} else if (!max_set) {
       12: 3719:			max = SvNV(ST(i));
call    0 returned 12
       12: 3720:			max_set = 1;
        -: 3721:		} else {
       12: 3722:			croak("Too many arguments or unrecognized parameter passed to runif()");
        -: 3723:		}
        -: 3724:		i++;
        -: 3725:	}
        -: 3726:	if (!n_set) {
function XS_Stats__LikeR_rbinom called 36 returned 58% blocks executed 90%
       36: 3727:		croak("runif() requires at least the 'n' parameter");
        -: 3728:	}
       36: 3729:	// Ensure PRNG is seeded
       36: 3729-block  0
call    0 returned 36
branch  1 taken 0 (fallthrough)
branch  2 taken 36
        -: 3730:	AUTO_SEED_PRNG();
        -: 3731:	AV *restrict results = newAV();
        -: 3732:	if (n > 0) {
        -: 3733:		av_extend(results, n - 1);
        -: 3734:	}
        -: 3735:	const double range = max - min;
        -: 3736:	for (size_t j = 0; j < n; j++) {
        -: 3737:		double r;
        -: 3738:		if (max < min) {
        -: 3739:			r = NAN; // R behavior for inverted ranges
        -: 3740:		} else {
        -: 3741:			r = min + range * Drand01();
        -: 3742:		}
        -: 3743:		av_push(results, newSVnv(r));
        -: 3744:	}
        -: 3745:	RETVAL = newRV_noinc((SV*)results);
        -: 3746:}
        -: 3747:#line 3748 "LikeR.c"
        -: 3748:	RETVAL = sv_2mortal(RETVAL);
        -: 3749:	ST(0) = RETVAL;
        -: 3750:    }
        -: 3751:    XSRETURN(1);
        -: 3752:}
        -: 3753:
        -: 3754:
        -: 3755:XS_EUPXS(XS_Stats__LikeR_rbinom); /* prototype to pass -Wmissing-prototypes */
        -: 3756:XS_EUPXS(XS_Stats__LikeR_rbinom)
        -: 3757:{
        -: 3758:    dVAR; dXSARGS;
        -: 3759:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3760:    PERL_UNUSED_VAR(items); /* -W */
        -: 3761:    {
        -: 3762:	SV *	RETVAL;
        -: 3763:#line 3420 "LikeR.xs"
        -: 3764:	{
        -: 3765:	  // Auto-seed the PRNG if the Perl script hasn't done so yet
        -: 3766:	  AUTO_SEED_PRNG();
        -: 3767:	  if (items % 2 != 0)
        -: 3768:		   croak("Usage: rbinom(n => 10, size => 100, prob => 0.5)");
        -: 3769:	  //Parse named arguments
        -: 3770:	  size_t n = 0, size = 0;
       21: 3771:	  double prob = 0.5;
call    0 returned 21
       21: 3772:
        -: 3773:	  bool size_set = false, prob_set = false;
       21: 3774:
        -: 3775:	  for (unsigned short i = 0; i < items; i += 2) {
        -: 3776:		   const char* restrict key = SvPV_nolen(ST(i));
        -: 3777:		   SV* restrict val = ST(i + 1);
        -: 3778:
function XS_Stats__LikeR_hist called 27 returned 56% blocks executed 83%
       27: 3779:		   if      (strEQ(key, "n"))      n    = (unsigned int)SvUV(val);
        -: 3780:		   else if (strEQ(key, "size")) { size = (unsigned int)SvUV(val); size_set = true; }
       27: 3781:		   else if (strEQ(key, "prob")) { prob = SvNV(val); prob_set = true; }
       27: 3781-block  0
call    0 returned 27
       27: 3782:		   else croak("rbinom: unknown argument '%s'", key);
branch  0 taken 0 (fallthrough)
branch  1 taken 27
    #####: 3783:	  }
    %%%%%: 3783-block  0
call    0 never executed
        -: 3784:
        -: 3785:	  // R requires size and prob to be explicitly passed in rbinom
       27: 3786:	  if (!size_set || !prob_set) croak("rbinom: 'size' and 'prob' are required arguments");
       27: 3786-block  0
branch  0 taken 24 (fallthrough)
branch  1 taken 3
        -: 3787:	  if (prob < 0.0 || prob > 1.0) croak("rbinom: prob must be between 0 and 1");
        -: 3788:
        -: 3789:	  AV *restrict result_av = newAV();
        -: 3790:	  if (n > 0) {
        -: 3791:		   av_extend(result_av, n - 1);
        -: 3792:		   for (unsigned int i = 0; i < n; i++) {
        -: 3793:		       av_store(result_av, i, newSVuv(generate_binomial(size, prob)));
        -: 3794:		   }
        -: 3795:	  }
        -: 3796:
        -: 3797:	  RETVAL = newRV_noinc((SV*)result_av);
        -: 3798:	}
        -: 3799:#line 3800 "LikeR.c"
        -: 3800:	RETVAL = sv_2mortal(RETVAL);
        -: 3801:	ST(0) = RETVAL;
        -: 3802:    }
        -: 3803:    XSRETURN(1);
        -: 3804:}
        -: 3805:
        -: 3806:
        -: 3807:XS_EUPXS(XS_Stats__LikeR_hist); /* prototype to pass -Wmissing-prototypes */
        -: 3808:XS_EUPXS(XS_Stats__LikeR_hist)
        -: 3809:{
        -: 3810:    dVAR; dXSARGS;
        -: 3811:    if (items < 1)
        -: 3812:       croak_xs_usage(cv,  "x_sv, ...");
        -: 3813:    {
        -: 3814:	SV *	RETVAL;
        -: 3815:	SV*	x_sv = ST(0)
        -: 3816:;
        -: 3817:#line 3461 "LikeR.xs"
        -: 3818:	{
        -: 3819:		// 1. Validate Input
        -: 3820:		if (!SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 3821:			croak("hist: first argument must be an array reference");
        -: 3822:
        -: 3823:		AV*restrict x_av = (AV*)SvRV(x_sv);
        -: 3824:		size_t n_raw = av_len(x_av) + 1;
        -: 3825:		if (n_raw == 0) croak("hist: input array is empty");
        -: 3826:
        -: 3827:		// 2. Extract Data & Find Range
        -: 3828:		double *restrict x;
        -: 3829:		Newx(x, n_raw, double);
        -: 3830:		size_t n = 0;
        -: 3831:		double min_val = DBL_MAX, max_val = -DBL_MAX;
        -: 3832:
        -: 3833:		for (size_t i = 0; i < n_raw; i++) {
        -: 3834:			SV**restrict tv = av_fetch(x_av, i, 0);
        -: 3835:			if (tv && SvOK(*tv)) {
        -: 3836:				 double val = SvNV(*tv);
        -: 3837:				 x[n++] = val;
        -: 3838:				 if (val < min_val) min_val = val;
        -: 3839:				 if (val > max_val) max_val = val;
        -: 3840:			}
        -: 3841:		}
        -: 3842:		if (n == 0) {
        -: 3843:			Safefree(x);
        -: 3844:			croak("hist: input contains no valid numeric data");
        -: 3845:		}
        -: 3846:		// 3. Determine Bin Count (Sturges default or user-provided)
        -: 3847:		size_t n_bins = 0;
        -: 3848:
        -: 3849:		if (items == 2) {
        -: 3850:			// Support pure positional argument: hist($data, 22)
        -: 3851:			n_bins = (size_t)SvIV(ST(1));
        -: 3852:		} else if (items > 2) {
        -: 3853:			/* Support named parameters even if mixed with positional arguments */
        -: 3854:			for (unsigned short i = 1; i < items - 1; i++) {
        -: 3855:				 /* Make sure the SV holds a string before doing string comparison */
        -: 3856:				 if (SvPOK(ST(i)) && strEQ(SvPV_nolen(ST(i)), "breaks")) {
        -: 3857:				     n_bins = (size_t)SvIV(ST(i+1));
        -: 3858:				     break;
        -: 3859:				 }
        -: 3860:			}
        -: 3861:			/* Fallback: if 'breaks' wasn't found but a positional number was given first */
        -: 3862:			if (n_bins == 0 && looks_like_number(ST(1))) {
        -: 3863:				 n_bins = (size_t)SvIV(ST(1));
        -: 3864:			}
        -: 3865:		}
        -: 3866:		if (n_bins == 0) n_bins = calculate_sturges_bins(n);
        -: 3867:		// 4. Allocate Result Arrays
        -: 3868:		double *restrict breaks, *restrict mids, *restrict density;
        -: 3869:		size_t *restrict counts;
        -: 3870:		Newx(breaks,  n_bins + 1, double);
        -: 3871:		Newx(mids,    n_bins,     double);
        -: 3872:		Newx(density, n_bins,     double);
        -: 3873:		Newx(counts,  n_bins,     size_t);
        -: 3874:
        -: 3875:		// Generate simple linear breaks
        -: 3876:		double step = (max_val - min_val) / (double)n_bins;
        -: 3877:		for (size_t i = 0; i <= n_bins; i++) {
        -: 3878:			breaks[i] = min_val + (double)i * step;
        -: 3879:		}
        -: 3880:
       15: 3881:		// 5. Compute Statistics
call    0 returned 15
       15: 3882:		compute_hist_logic(x, n, breaks, n_bins, counts, mids, density);
        -: 3883:
       15: 3884:		// 6. Build Return HashRef
        -: 3885:		HV*restrict res_hv = newHV();
        -: 3886:		AV*restrict av_breaks  = newAV();
        -: 3887:		AV*restrict av_counts  = newAV();
        -: 3888:		AV*restrict av_mids    = newAV();
function XS_Stats__LikeR_quantile called 12 returned 100% blocks executed 81%
       12: 3889:		AV*restrict av_density = newAV();
        -: 3890:		for (size_t i = 0; i <= n_bins; i++) {
       12: 3891:			av_push(av_breaks, newSVnv(breaks[i]));
       12: 3891-block  0
call    0 returned 12
branch  1 taken 12 (fallthrough)
branch  2 taken 0
        -: 3892:			if (i < n_bins) {
        -: 3893:				 av_push(av_counts,  newSViv(counts[i]));
        -: 3894:				 av_push(av_mids,    newSVnv(mids[i]));
        -: 3895:				 av_push(av_density, newSVnv(density[i]));
        -: 3896:			}
        -: 3897:		}
        -: 3898:		hv_stores(res_hv, "breaks",  newRV_noinc((SV*)av_breaks));
        -: 3899:		hv_stores(res_hv, "counts",  newRV_noinc((SV*)av_counts));
        -: 3900:		hv_stores(res_hv, "mids",    newRV_noinc((SV*)av_mids));
        -: 3901:		hv_stores(res_hv, "density", newRV_noinc((SV*)av_density));
        -: 3902:
        -: 3903:		// Clean
        -: 3904:		Safefree(x); Safefree(breaks); Safefree(mids);
        -: 3905:		Safefree(density); Safefree(counts);
        -: 3906:
        -: 3907:		RETVAL = newRV_noinc((SV*)res_hv);
        -: 3908:	}
        -: 3909:#line 3910 "LikeR.c"
        -: 3910:	RETVAL = sv_2mortal(RETVAL);
        -: 3911:	ST(0) = RETVAL;
        -: 3912:    }
        -: 3913:    XSRETURN(1);
        -: 3914:}
        -: 3915:
        -: 3916:
        -: 3917:XS_EUPXS(XS_Stats__LikeR_quantile); /* prototype to pass -Wmissing-prototypes */
        -: 3918:XS_EUPXS(XS_Stats__LikeR_quantile)
        -: 3919:{
        -: 3920:    dVAR; dXSARGS;
        -: 3921:    PERL_UNUSED_VAR(cv); /* -W */
        -: 3922:    PERL_UNUSED_VAR(items); /* -W */
        -: 3923:    {
        -: 3924:	SV *	RETVAL;
        -: 3925:#line 3557 "LikeR.xs"
        -: 3926:	{
        -: 3927:		SV *restrict x_sv = NULL;
        -: 3928:		SV *restrict probs_sv = NULL;
        -: 3929:		int arg_idx = 0;
        -: 3930:
        -: 3931:		/* --- 1. Consume first positional arg as 'x' if it's an array ref --- */
        -: 3932:		if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 3933:			 x_sv = ST(arg_idx);
        -: 3934:			 arg_idx++;
        -: 3935:		}
        -: 3936:
        -: 3937:		/* --- 2. Remaining args must be key-value pairs --- */
        -: 3938:		if ((items - arg_idx) % 2 != 0)
        -: 3939:			 croak("Usage: quantile(\\@data, probs => \\@probs)  OR  quantile(x => \\@data, probs => \\@probs)");
        -: 3940:
        -: 3941:		for (; arg_idx < items; arg_idx += 2) {
        -: 3942:			 const char *restrict key = SvPV_nolen(ST(arg_idx));
        -: 3943:			 SV *restrict val = ST(arg_idx + 1);
        -: 3944:
        -: 3945:			 if      (strEQ(key, "x"))     x_sv     = val;
        -: 3946:			 else if (strEQ(key, "probs")) probs_sv = val;
        -: 3947:			 else croak("quantile: unknown argument '%s'", key);
        -: 3948:		}
        -: 3949:		if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 3950:			croak("quantile: 'x' must be an array reference");
        -: 3951:		AV *restrict x_av = (AV*)SvRV(x_sv);
        -: 3952:		size_t n_raw = av_len(x_av) + 1;
        -: 3953:		if (n_raw == 0) croak("quantile: 'x' is empty");
        -: 3954:
        -: 3955:		/* --- Extract valid numeric data & drop NAs --- */
        -: 3956:		double *restrict x;
        -: 3957:		Newx(x, n_raw, double);
        -: 3958:		size_t n = 0;
        -: 3959:		for (size_t i = 0; i < n_raw; i++) {
        -: 3960:			SV **restrict tv = av_fetch(x_av, i, 0);
        -: 3961:			if (tv && SvOK(*tv)) {
        -: 3962:				 x[n++] = SvNV(*tv);
        -: 3963:			}
        -: 3964:		}
        -: 3965:		if (n == 0) {
        -: 3966:			Safefree(x);
        -: 3967:			croak("quantile: 'x' contains no valid numbers");
        -: 3968:		}
        -: 3969:		// --- Sort Data for Quantile Math ---
        -: 3970:		qsort(x, n, sizeof(double), compare_doubles);
        -: 3971:		// --- Parse Probabilities (Default matches R's c(0, .25, .5, .75, 1)) ---
        -: 3972:		double default_probs[] = {0.0, 0.25, 0.50, 0.75, 1.0};
        -: 3973:		unsigned int n_probs = 5;
        -: 3974:		double *restrict probs;
        -: 3975:
        -: 3976:		if (probs_sv && SvROK(probs_sv) && SvTYPE(SvRV(probs_sv)) == SVt_PVAV) {
        -: 3977:			AV *restrict p_av = (AV*)SvRV(probs_sv);
        -: 3978:			n_probs = av_len(p_av) + 1;
        -: 3979:			Newx(probs, n_probs, double);
        -: 3980:			for (unsigned int i = 0; i < n_probs; i++) {
        -: 3981:				 SV **tv = av_fetch(p_av, i, 0);
        -: 3982:				 probs[i] = (tv && SvOK(*tv)) ? SvNV(*tv) : 0.0;
        -: 3983:				 if (probs[i] < 0.0 || probs[i] > 1.0) {
        -: 3984:				     Safefree(x); Safefree(probs);
        -: 3985:				     croak("quantile: probabilities must be between 0 and 1");
        -: 3986:				 }
        -: 3987:			}
        -: 3988:		} else {
        -: 3989:			Newx(probs, n_probs, double);
        -: 3990:			for (unsigned int i = 0; i < n_probs; i++) probs[i] = default_probs[i];
        -: 3991:		}
        -: 3992:
        -: 3993:		/* --- Calculate Quantiles (R Type 7 Algorithm) --- */
        -: 3994:		HV *restrict res_hv = newHV();
        -: 3995:
        -: 3996:		for (size_t i = 0; i < n_probs; i++) {
        -: 3997:			double p = probs[i], q = 0.0;
        -: 3998:
        -: 3999:			if (n == 1) {
        -: 4000:				 q = x[0];
        -: 4001:			} else if (p == 1.0) {
        -: 4002:				 q = x[n - 1]; /* Prevent out-of-bounds mapping */
       12: 4003:			} else if (p == 0.0) {
call    0 returned 12
       12: 4004:				 q = x[0];
        -: 4005:			} else {
       12: 4006:				 /* Continuous sample quantile interpolation (Type 7) */
        -: 4007:				 double h = (n - 1) * p;
        -: 4008:				 unsigned int j = (unsigned int)h; /* floor via cast */
        -: 4009:				 double gamma = h - j;
        -: 4010:				 q = (1.0 - gamma) * x[j] + gamma * x[j + 1];
function XS_Stats__LikeR_mean called 117 returned 97% blocks executed 97%
      117: 4011:			}
        -: 4012:
      117: 4013:			/* Format hash key to exactly match R's naming convention ("25%", "33.3%") */
      117: 4013-block  0
call    0 returned 117
        -: 4014:			char key[32];
        -: 4015:			double pct = p * 100.0;
        -: 4016:
        -: 4017:			if (pct == (unsigned int)pct) {
     117*: 4018:				 snprintf(key, sizeof(key), "%.0f%%", pct);
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:			} else {
        -: 4020:				 snprintf(key, sizeof(key), "%.1f%%", pct);
        -: 4021:			}
        -: 4022:
        -: 4023:			hv_store(res_hv, key, strlen(key), newSVnv(q), 0);
        -: 4024:		}
        -: 4025:
        -: 4026:		Safefree(x);
        -: 4027:		Safefree(probs);
        -: 4028:
        -: 4029:		RETVAL = newRV_noinc((SV*)res_hv);
        -: 4030:	}
        -: 4031:#line 4032 "LikeR.c"
        -: 4032:	RETVAL = sv_2mortal(RETVAL);
        -: 4033:	ST(0) = RETVAL;
        -: 4034:    }
        -: 4035:    XSRETURN(1);
        -: 4036:}
        -: 4037:
        -: 4038:
        -: 4039:XS_EUPXS(XS_Stats__LikeR_mean); /* prototype to pass -Wmissing-prototypes */
      114: 4040:XS_EUPXS(XS_Stats__LikeR_mean)
      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:{
        -: 4042:    dVAR; dXSARGS;
      114: 4043:    PERL_UNUSED_VAR(cv); /* -W */
      114: 4043-block  0
        -: 4044:    PERL_UNUSED_VAR(items); /* -W */
        -: 4045:    {
        -: 4046:	double	RETVAL;
        -: 4047:	dXSTARG;
function XS_Stats__LikeR_sd called 15 returned 80% blocks executed 93%
       15: 4048:#line 3669 "LikeR.xs"
        -: 4049:	  double total = 0;
       15: 4050:	  size_t count = 0;
       15: 4050-block  0
call    0 returned 15
        -: 4051:#line 4052 "LikeR.c"
        -: 4052:#line 3672 "LikeR.xs"
        -: 4053:		for (size_t i = 0; i < items; i++) {
        -: 4054:			SV* restrict arg = ST(i);
      15*: 4055:			if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
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:				AV* restrict av = (AV*)SvRV(arg);
        -: 4057:				size_t len = av_len(av) + 1;
        -: 4058:				for (size_t j = 0; j < len; j++) {
        -: 4059:				     SV** restrict tv = av_fetch(av, j, 0);
        -: 4060:				     if (tv && SvOK(*tv)) {
        -: 4061:				         total += SvNV(*tv);
        -: 4062:				         count++;
        -: 4063:				     } else {
        -: 4064:				         croak("mean: undefined value at array ref index %zu (argument %zu)", j, i);
        -: 4065:				     }
        -: 4066:				}
        -: 4067:			} else if (SvOK(arg)) {
        -: 4068:				 total += SvNV(arg);
        -: 4069:				 count++;
        -: 4070:			} else {
        -: 4071:				 croak("mean: undefined value at argument index %zu", i);
        -: 4072:			}
        -: 4073:		}
        -: 4074:		if (count == 0) croak("mean needs >= 1 element");
        -: 4075:		RETVAL = total / count;
        -: 4076:#line 4077 "LikeR.c"
        -: 4077:	TARGn((double)RETVAL, 1);
        -: 4078:	ST(0) = TARG;
        -: 4079:    }
        -: 4080:    XSRETURN(1);
        -: 4081:}
        -: 4082:
        -: 4083:
        -: 4084:XS_EUPXS(XS_Stats__LikeR_sum); /* prototype to pass -Wmissing-prototypes */
        -: 4085:XS_EUPXS(XS_Stats__LikeR_sum)
        -: 4086:{
        -: 4087:    dVAR; dXSARGS;
      12*: 4088:    PERL_UNUSED_VAR(cv); /* -W */
       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:    PERL_UNUSED_VAR(items); /* -W */
        -: 4090:    {
       12: 4091:	double	RETVAL;
       12: 4091-block  0
        -: 4092:	dXSTARG;
        -: 4093:#line 3701 "LikeR.xs"
        -: 4094:	  double total = 0;
        -: 4095:	  size_t count = 0;
function XS_Stats__LikeR_var called 18 returned 83% blocks executed 97%
       18: 4096:#line 4097 "LikeR.c"
        -: 4097:#line 3704 "LikeR.xs"
       18: 4098:	  for (size_t i = 0; i < items; i++) {
       18: 4098-block  0
call    0 returned 18
        -: 4099:		   SV* restrict arg = ST(i);
        -: 4100:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4101:		       AV* restrict av = (AV*)SvRV(arg);
        -: 4102:		       size_t len = av_len(av) + 1;
      18*: 4103:		       for (size_t j = 0; j < len; j++) {
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:		           SV** restrict tv = av_fetch(av, j, 0);
        -: 4105:		           if (tv && SvOK(*tv)) {
        -: 4106:		               total += SvNV(*tv);
        -: 4107:		               count++;
        -: 4108:		           } else {
        -: 4109:		               croak("sum: undefined value at array ref index %zu (argument %zu)", j, i);
        -: 4110:		           }
        -: 4111:		       }
        -: 4112:		   } else if (SvOK(arg)) {
        -: 4113:		       total += SvNV(arg);
        -: 4114:		       count++;
        -: 4115:		   } else {
        -: 4116:		       croak("sum: undefined value at argument index %zu", i);
        -: 4117:		   }
        -: 4118:	  }
        -: 4119:	  if (count == 0) croak("sum needs >= 1 element");
        -: 4120:	  RETVAL = total;
        -: 4121:#line 4122 "LikeR.c"
        -: 4122:	TARGn((double)RETVAL, 1);
        -: 4123:	ST(0) = TARG;
        -: 4124:    }
        -: 4125:    XSRETURN(1);
        -: 4126:}
        -: 4127:
        -: 4128:
        -: 4129:XS_EUPXS(XS_Stats__LikeR_sd); /* prototype to pass -Wmissing-prototypes */
        -: 4130:XS_EUPXS(XS_Stats__LikeR_sd)
        -: 4131:{
        -: 4132:    dVAR; dXSARGS;
        -: 4133:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4134:    PERL_UNUSED_VAR(items); /* -W */
        -: 4135:    {
       15: 4136:	double	RETVAL;
       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:	dXSTARG;
        -: 4138:#line 3733 "LikeR.xs"
       15: 4139:        double mean = 0.0, M2 = 0.0;
       15: 4139-block  0
        -: 4140:        size_t count = 0;
        -: 4141:#line 4142 "LikeR.c"
        -: 4142:#line 3736 "LikeR.xs"
        -: 4143:        /* Single Pass Standard Deviation via Welford's Algorithm */
function XS_Stats__LikeR_t_test called 141 returned 89% blocks executed 91%
      141: 4144:        for (size_t i = 0; i < items; i++) {
        -: 4145:            SV* restrict arg = ST(i);
      141: 4146:            if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
      141: 4146-block  0
call    0 returned 141
branch  1 taken 141 (fallthrough)
branch  2 taken 0
        -: 4147:                AV* restrict av = (AV*)SvRV(arg);
        -: 4148:                size_t len = av_len(av) + 1;
        -: 4149:                for (size_t j = 0; j < len; j++) {
        -: 4150:                    SV** restrict tv = av_fetch(av, j, 0);
        -: 4151:                    if (tv && SvOK(*tv)) {
        -: 4152:                        count++;
        -: 4153:                        double val = SvNV(*tv);
        -: 4154:                        double delta = val - mean;
        -: 4155:                        mean += delta / count;
        -: 4156:                        M2 += delta * (val - mean);
        -: 4157:                    } else {
        -: 4158:                        croak("sd: undefined value at array ref index %zu (argument %zu)", j, i);
        -: 4159:                    }
        -: 4160:                }
        -: 4161:            } else if (SvOK(arg)) {
        -: 4162:                count++;
        -: 4163:                double val = SvNV(arg);
        -: 4164:                double delta = val - mean;
        -: 4165:                mean += delta / count;
        -: 4166:                M2 += delta * (val - mean);
        -: 4167:            } else {
        -: 4168:                croak("sd: undefined value at argument index %zu", i);
        -: 4169:            }
        -: 4170:        }
        -: 4171:        if (count < 2) croak("sd needs >= 2 elements");
        -: 4172:        RETVAL = sqrt(M2 / (count - 1));
        -: 4173:#line 4174 "LikeR.c"
        -: 4174:	TARGn((double)RETVAL, 1);
        -: 4175:	ST(0) = TARG;
        -: 4176:    }
        -: 4177:    XSRETURN(1);
        -: 4178:}
        -: 4179:
        -: 4180:
        -: 4181:XS_EUPXS(XS_Stats__LikeR_var); /* prototype to pass -Wmissing-prototypes */
        -: 4182:XS_EUPXS(XS_Stats__LikeR_var)
        -: 4183:{
        -: 4184:    dVAR; dXSARGS;
        -: 4185:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4186:    PERL_UNUSED_VAR(items); /* -W */
        -: 4187:    {
        -: 4188:	double	RETVAL;
        -: 4189:	dXSTARG;
        -: 4190:#line 3773 "LikeR.xs"
        -: 4191:	  double mean = 0.0, M2 = 0.0;
        -: 4192:	  size_t count = 0;
        -: 4193:#line 4194 "LikeR.c"
        -: 4194:#line 3776 "LikeR.xs"
        -: 4195:	  /* Single Pass Variance via Welford's Algorithm */
        -: 4196:	  for (size_t i = 0; i < items; i++) {
        -: 4197:		   SV* restrict arg = ST(i);
        -: 4198:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4199:		       AV* restrict av = (AV*)SvRV(arg);
        -: 4200:		       size_t len = av_len(av) + 1;
        -: 4201:		       for (size_t j = 0; j < len; j++) {
        -: 4202:		           SV** restrict tv = av_fetch(av, j, 0);
        -: 4203:		           if (tv && SvOK(*tv)) {
        -: 4204:		               count++;
        -: 4205:		               double val = SvNV(*tv);
        -: 4206:		               double delta = val - mean;
        -: 4207:		               mean += delta / count;
        -: 4208:		               M2 += delta * (val - mean);
        -: 4209:		           } else {
        -: 4210:		               croak("var: undefined value at array ref index %zu (argument %zu)", j, i);
        -: 4211:		           }
        -: 4212:		       }
        -: 4213:		   } else if (SvOK(arg)) {
        -: 4214:		       count++;
        -: 4215:		       double val = SvNV(arg);
        -: 4216:		       double delta = val - mean;
        -: 4217:		       mean += delta / count;
        -: 4218:		       M2 += delta * (val - mean);
        -: 4219:		   } else {
        -: 4220:		       croak("var: undefined value at argument index %zu", i);
        -: 4221:		   }
        -: 4222:	  }
        -: 4223:	  if (count < 2) croak("var needs >= 2 elements");
        -: 4224:	  RETVAL = M2 / (count - 1);
        -: 4225:#line 4226 "LikeR.c"
        -: 4226:	TARGn((double)RETVAL, 1);
        -: 4227:	ST(0) = TARG;
        -: 4228:    }
        -: 4229:    XSRETURN(1);
        -: 4230:}
        -: 4231:
        -: 4232:
        -: 4233:XS_EUPXS(XS_Stats__LikeR_t_test); /* prototype to pass -Wmissing-prototypes */
        -: 4234:XS_EUPXS(XS_Stats__LikeR_t_test)
        -: 4235:{
        -: 4236:    dVAR; dXSARGS;
        -: 4237:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4238:    PERL_UNUSED_VAR(items); /* -W */
        -: 4239:    {
        -: 4240:	SV *	RETVAL;
        -: 4241:#line 3811 "LikeR.xs"
        -: 4242:	{
        -: 4243:		SV*restrict x_sv = NULL;
        -: 4244:		SV*restrict y_sv = NULL;
        -: 4245:		double mu = 0.0, conf_level = 0.95;
        -: 4246:		bool paired = FALSE, var_equal = FALSE;
        -: 4247:		const char*restrict alternative = "two.sided";
        -: 4248:
        -: 4249:		int arg_idx = 0;
        -: 4250:
        -: 4251:		// 1. Shift first positional argument as 'x' if it's an array reference
        -: 4252:		if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 4253:		  x_sv = ST(arg_idx);
        -: 4254:		  arg_idx++;
        -: 4255:		}
        -: 4256:
        -: 4257:		// 2. Shift second positional argument as 'y' if it's an array reference
        -: 4258:		if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 4259:		  y_sv = ST(arg_idx);
        -: 4260:		  arg_idx++;
        -: 4261:		}
        -: 4262:
        -: 4263:		// Ensure the remaining arguments form complete key-value pairs
        -: 4264:		if ((items - arg_idx) % 2 != 0) {
        -: 4265:		  croak("Usage: t_test(\\@x, [\\@y], key => value, ...)");
        -: 4266:		}
        -: 4267:
        -: 4268:		// --- Parse named arguments from the remaining flat stack ---
        -: 4269:		for (; arg_idx < items; arg_idx += 2) {
        -: 4270:			const char*restrict key = SvPV_nolen(ST(arg_idx));
        -: 4271:			SV*restrict val = ST(arg_idx + 1);
        -: 4272:
        -: 4273:			if      (strEQ(key, "x"))           x_sv        = val;
        -: 4274:			else if (strEQ(key, "y"))           y_sv        = val;
        -: 4275:			else if (strEQ(key, "mu"))          mu          = SvNV(val);
        -: 4276:			else if (strEQ(key, "paired"))      paired      = SvTRUE(val);
        -: 4277:			else if (strEQ(key, "var_equal"))   var_equal   = SvTRUE(val);
        -: 4278:			else if (strEQ(key, "conf_level"))  conf_level  = SvNV(val);
        -: 4279:			else if (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 4280:			else croak("t_test: unknown argument '%s'", key);
        -: 4281:		}
        -: 4282:
        -: 4283:		// --- Validate required / types ---
        -: 4284:		if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 4285:			croak("t_test: 'x' is a required argument and must be an ARRAY reference");
        -: 4286:		AV*restrict x_av = (AV*)SvRV(x_sv);
        -: 4287:		size_t nx = av_len(x_av) + 1;
        -: 4288:		if (nx < 2) croak("t_test: 'x' needs at least 2 elements");
        -: 4289:		AV*restrict y_av = NULL;
        -: 4290:		if (y_sv && SvROK(y_sv) && SvTYPE(SvRV(y_sv)) == SVt_PVAV)
        -: 4291:			y_av = (AV*)SvRV(y_sv);
        -: 4292:
        -: 4293:		if (conf_level <= 0.0 || conf_level >= 1.0)
        -: 4294:			croak("t_test: 'conf_level' must be between 0 and 1");
        -: 4295:		// --- Computation via Welford's Algorithm --- */
        -: 4296:		double mean_x = 0.0, M2_x = 0.0, var_x, t_stat, df, p_val, std_err, cint_est;
        -: 4297:		HV*restrict results = newHV();
        -: 4298:		for (size_t i = 0; i < nx; i++) {
        -: 4299:			SV**restrict tv = av_fetch(x_av, i, 0);
        -: 4300:			double val = (tv && SvOK(*tv)) ? SvNV(*tv) : 0;
        -: 4301:			double delta = val - mean_x;
        -: 4302:			mean_x += delta / (i + 1);
      126: 4303:			M2_x += delta * (val - mean_x);
call    0 returned 126
      126: 4304:		}
        -: 4305:		var_x = M2_x / (nx - 1);
      126: 4306:		if (var_x == 0.0 && !y_av) croak("t_test: data are essentially constant");
        -: 4307:
        -: 4308:		if (paired || y_av) {
        -: 4309:			if (!y_av) croak("t_test: 'y' must be provided for paired or two-sample tests");
        -: 4310:			size_t ny = av_len(y_av) + 1;
function XS_Stats__LikeR_p_adjust called 45 returned 87% blocks executed 90%
       45: 4311:			if (paired && ny != nx) croak("t_test: Paired arrays must be same length");
        -: 4312:			double mean_y = 0.0, M2_y = 0.0, var_y;
       45: 4313:			for (size_t i = 0; i < ny; i++) {
       45: 4313-block  0
call    0 returned 45
       45: 4314:				 SV**restrict tv = av_fetch(y_av, i, 0);
branch  0 taken 45 (fallthrough)
branch  1 taken 0
       45: 4314-block  0
branch  2 taken 0 (fallthrough)
branch  3 taken 45
    #####: 4315:				 double val = (tv && SvOK(*tv)) ? SvNV(*tv) : 0;
    %%%%%: 4315-block  0
call    0 never executed
        -: 4316:				 double delta = val - mean_y;
       45: 4317:				 mean_y += delta / (i + 1);
        -: 4318:				 M2_y += delta * (val - mean_y);
       45: 4319:			}
        -: 4320:			var_y = M2_y / (ny - 1);
        -: 4321:			if (paired) {
        -: 4322:				 double mean_d = 0.0, M2_d = 0.0;
       45: 4323:				 for (size_t i = 0; i < nx; i++) {
       45: 4323-block  0
branch  0 taken 6 (fallthrough)
branch  1 taken 39
        6: 4324:					  SV**restrict dx_ptr = av_fetch(x_av, i, 0);
        6: 4324-block  0
        -: 4325:					  SV**restrict dy_ptr = av_fetch(y_av, i, 0);
       39: 4326:				     double dx = (dx_ptr && SvOK(*dx_ptr)) ? SvNV(*dx_ptr) : 0.0;
       39: 4326-block  0
call    0 returned 39
        -: 4327:				     double dy = (dy_ptr && SvOK(*dy_ptr)) ? SvNV(*dy_ptr) : 0.0;
        -: 4328:				     double val = dx - dy;
        -: 4329:				     double delta = val - mean_d;
        -: 4330:				     mean_d += delta / (i + 1);
        -: 4331:				     M2_d += delta * (val - mean_d);
        -: 4332:				 }
        -: 4333:				 double var_d = M2_d / (nx - 1);
        -: 4334:				 if (var_d == 0.0) croak("t_test: data are essentially constant");
        -: 4335:				 cint_est = mean_d;
        -: 4336:				 std_err  = sqrt(var_d / nx);
        -: 4337:				 t_stat   = (cint_est - mu) / std_err;
        -: 4338:				 df       = nx - 1;
        -: 4339:				 hv_store(results, "estimate", 8, newSVnv(mean_d), 0);
        -: 4340:			} else if (var_equal) {
        -: 4341:				 if (var_x == 0.0 && var_y == 0.0) croak("t_test: data are essentially constant");
        -: 4342:				 double pooled_var = ((nx - 1) * var_x + (ny - 1) * var_y) / (nx + ny - 2);
        -: 4343:				 cint_est = mean_x - mean_y;
        -: 4344:				 std_err  = sqrt(pooled_var * (1.0 / nx + 1.0 / ny));
        -: 4345:				 t_stat   = (cint_est - mu) / std_err;
        -: 4346:				 df       = nx + ny - 2;
        -: 4347:				 hv_store(results, "estimate_x", 10, newSVnv(mean_x), 0);
        -: 4348:				 hv_store(results, "estimate_y", 10, newSVnv(mean_y), 0);
        -: 4349:			} else {
        -: 4350:				 if (var_x == 0.0 && var_y == 0.0) croak("t_test: data are essentially constant");
        -: 4351:				 cint_est         = mean_x - mean_y;
        -: 4352:				 double stderr_x2 = var_x / nx;
        -: 4353:				 double stderr_y2 = var_y / ny;
        -: 4354:				 std_err          = sqrt(stderr_x2 + stderr_y2);
        -: 4355:				 t_stat           = (cint_est - mu) / std_err;
        -: 4356:				 df = pow(stderr_x2 + stderr_y2, 2) /
        -: 4357:				      (pow(stderr_x2, 2) / (nx - 1) + pow(stderr_y2, 2) / (ny - 1));
        -: 4358:				 hv_store(results, "estimate_x", 10, newSVnv(mean_x), 0);
        -: 4359:				 hv_store(results, "estimate_y", 10, newSVnv(mean_y), 0);
        -: 4360:			}
        -: 4361:		} else {
        -: 4362:			cint_est = mean_x;
        -: 4363:			std_err  = sqrt(var_x / nx);
        -: 4364:			t_stat   = (cint_est - mu) / std_err;
        -: 4365:			df       = nx - 1;
        -: 4366:			hv_store(results, "estimate", 8, newSVnv(mean_x), 0);
        -: 4367:		}
        -: 4368:		p_val = get_t_pvalue(t_stat, df, alternative);
        -: 4369:		double alpha = 1.0 - conf_level, t_crit, ci_lower, ci_upper;
        -: 4370:		if (strcmp(alternative, "less") == 0) {
        -: 4371:			t_crit   = qt_tail(df, alpha);
        -: 4372:			ci_lower = -INFINITY;
        -: 4373:			ci_upper = cint_est + t_crit * std_err;
        -: 4374:		} else if (strcmp(alternative, "greater") == 0) {
        -: 4375:			t_crit   = qt_tail(df, alpha);
        -: 4376:			ci_lower = cint_est - t_crit * std_err;
        -: 4377:			ci_upper = INFINITY;
        -: 4378:		} else {
        -: 4379:			t_crit   = qt_tail(df, alpha / 2.0);
        -: 4380:			ci_lower = cint_est - t_crit * std_err;
        -: 4381:			ci_upper = cint_est + t_crit * std_err;
        -: 4382:		}
        -: 4383:		AV*restrict conf_int = newAV();
        -: 4384:		av_push(conf_int, newSVnv(ci_lower));
        -: 4385:		av_push(conf_int, newSVnv(ci_upper));
        -: 4386:		hv_store(results, "statistic", 9, newSVnv(t_stat), 0);
        -: 4387:		hv_store(results, "df",        2, newSVnv(df),     0);
        -: 4388:		hv_store(results, "p_value",   7, newSVnv(p_val),  0);
        -: 4389:		hv_store(results, "conf_int",  8, newRV_noinc((SV*)conf_int), 0);
        -: 4390:		RETVAL = newRV_noinc((SV*)results);
        -: 4391:	}
        -: 4392:#line 4393 "LikeR.c"
        -: 4393:	RETVAL = sv_2mortal(RETVAL);
        -: 4394:	ST(0) = RETVAL;
        -: 4395:    }
        -: 4396:    XSRETURN(1);
        -: 4397:}
        -: 4398:
        -: 4399:
        -: 4400:XS_EUPXS(XS_Stats__LikeR_p_adjust); /* prototype to pass -Wmissing-prototypes */
        -: 4401:XS_EUPXS(XS_Stats__LikeR_p_adjust)
        -: 4402:{
        -: 4403:    dVAR; dXSARGS;
        -: 4404:    if (items < 1 || items > 2)
        -: 4405:       croak_xs_usage(cv,  "p_sv, method= \"holm\"");
        -: 4406:    PERL_UNUSED_VAR(ax); /* -Wall */
        -: 4407:    SP -= items;
        -: 4408:    {
        -: 4409:	SV *	p_sv = ST(0)
        -: 4410:;
        -: 4411:	const char *	method;
        -: 4412:
        -: 4413:	if (items < 2)
        -: 4414:	    method = "holm";
        -: 4415:	else {
        -: 4416:	    method = (const char *)SvPV_nolen(ST(1))
        -: 4417:;
        -: 4418:	}
        -: 4419:#line 3966 "LikeR.xs"
        -: 4420:		if (!SvROK(p_sv) || SvTYPE(SvRV(p_sv)) != SVt_PVAV) {
        -: 4421:			croak("p_adjust: first argument must be an ARRAY reference of p-values");
        -: 4422:		}
        -: 4423:		AV *restrict p_av = (AV*)SvRV(p_sv);
        -: 4424:		size_t n = av_len(p_av) + 1;
        -: 4425:		// Handle empty input
        -: 4426:		if (n == 0) {
        -: 4427:			XSRETURN_EMPTY;
        -: 4428:		}
        -: 4429:		// Normalize method string
        -: 4430:		char meth[64];
        -: 4431:		strncpy(meth, method, 63); meth[63] = '\0';
        -: 4432:		for(unsigned short int i = 0; meth[i]; i++) meth[i] = tolower(meth[i]);
        -: 4433:		// Resolve aliases
        -: 4434:		if (strstr(meth, "benjamini") && strstr(meth, "hochberg")) strcpy(meth, "bh");
        -: 4435:		if (strstr(meth, "benjamini") && strstr(meth, "yekutieli")) strcpy(meth, "by");
        -: 4436:		if (strcmp(meth, "fdr") == 0) strcpy(meth, "bh");
        -: 4437:		// Allocate C memory
        -: 4438:		PVal *restrict arr;
        -: 4439:		double *restrict adj;
        -: 4440:		Newx(arr, n, PVal);
        -: 4441:		Newx(adj, n, double);
        -: 4442:
        -: 4443:		for (size_t i = 0; i < n; i++) {
        -: 4444:			SV**restrict tv = av_fetch(p_av, i, 0);
        -: 4445:			arr[i].p = (tv && SvOK(*tv)) ? SvNV(*tv) : 1.0;
        -: 4446:			arr[i].orig_idx = i;
        -: 4447:		}
        -: 4448:		// Sort ascending (Stable sort using original index)
        -: 4449:		qsort(arr, n, sizeof(PVal), cmp_pval);
        -: 4450:#line 4451 "LikeR.c"
        -: 4451:#line 3997 "LikeR.xs"
        -: 4452:		if (strcmp(meth, "bonferroni") == 0) {
        -: 4453:			for (size_t i = 0; i < n; i++) {
        -: 4454:				double v = arr[i].p * n;
        -: 4455:				adj[arr[i].orig_idx] = (v < 1.0) ? v : 1.0;
        -: 4456:			}
        -: 4457:		} else if (strcmp(meth, "holm") == 0) {
        -: 4458:			double cummax = 0.0;
        -: 4459:			for (size_t i = 0; i < n; i++) {
        -: 4460:				 double v = arr[i].p * (n - i);
        -: 4461:				 if (v > cummax) cummax = v;
        -: 4462:				 adj[arr[i].orig_idx] = (cummax < 1.0) ? cummax : 1.0;
        -: 4463:			}
       36: 4464:		} else if (strcmp(meth, "hochberg") == 0) {
       36: 4465:			double cummin = 1.0;
        -: 4466:			for (ssize_t i = n - 1; i >= 0; i--) {
        -: 4467:				 double v = arr[i].p * (n - i);
        -: 4468:				 if (v < cummin) cummin = v;
        -: 4469:				 adj[arr[i].orig_idx] = (cummin < 1.0) ? cummin : 1.0;
        -: 4470:			}
function XS_Stats__LikeR_median called 18 returned 83% blocks executed 96%
       18: 4471:		} else if (strcmp(meth, "bh") == 0) {
        -: 4472:			double cummin = 1.0;
       18: 4473:			for (ssize_t i = n - 1; i >= 0; i--) {
       18: 4473-block  0
call    0 returned 18
        -: 4474:				double v = arr[i].p * n / (i + 1.0);
        -: 4475:				if (v < cummin) cummin = v;
        -: 4476:				adj[arr[i].orig_idx] = (cummin < 1.0) ? cummin : 1.0;
        -: 4477:			}
      18*: 4478:		} else if (strcmp(meth, "by") == 0) {
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:			double q = 0.0;
        -: 4480:			for (size_t i = 1; i <= n; i++) q += 1.0 / i;
        -: 4481:			double cummin = 1.0;
        -: 4482:			for (ssize_t i = n - 1; i >= 0; i--) {
        -: 4483:				double v = arr[i].p * n / (i + 1.0) * q;
        -: 4484:				if (v < cummin) cummin = v;
        -: 4485:				adj[arr[i].orig_idx] = (cummin < 1.0) ? cummin : 1.0;
        -: 4486:			}
        -: 4487:		} else if (strcmp(meth, "hommel") == 0) {
        -: 4488:			double *restrict pa, *restrict q_arr;
        -: 4489:			Newx(pa, n, double);
        -: 4490:			Newx(q_arr, n, double);
        -: 4491:			// Initial: min(n * p[i] / (i + 1))
        -: 4492:			double min_val = n * arr[0].p;
        -: 4493:			for (size_t i = 1; i < n; i++) {
        -: 4494:				double temp = (n * arr[i].p) / (i + 1.0);
        -: 4495:				if (temp < min_val) {
        -: 4496:				   min_val = temp;
        -: 4497:				}
        -: 4498:			}
        -: 4499:			// pa <- q <- rep(min, n)
        -: 4500:			for (size_t i = 0; i < n; i++) {
        -: 4501:				 pa[i] = min_val;
        -: 4502:				 q_arr[i] = min_val;
        -: 4503:			}
        -: 4504:			for (size_t j = n - 1; j >= 2; j--) {
        -: 4505:				 ssize_t n_mj = n - j;       // Max index for 'ij'. Length is n_mj + 1
        -: 4506:				 ssize_t i2_len = j - 1;     // Length of 'i2
        -: 4507:				 // Calculate q1 = min(j * p[i2] / (2:j))
        -: 4508:				 double q1 = (j * arr[n_mj + 1].p) / 2.0;
        -: 4509:				 for (size_t k = 1; k < i2_len; k++) {
        -: 4510:				     double temp_q1 = (j * arr[n_mj + 1 + k].p) / (2.0 + k);
        -: 4511:				     if (temp_q1 < q1) {
        -: 4512:				         q1 = temp_q1;
        -: 4513:				     }
        -: 4514:				 }
        -: 4515:				 // q[ij] <- pmin(j * p[ij], q1)
        -: 4516:				 for (size_t i = 0; i <= n_mj; i++) {
        -: 4517:				     double v = j * arr[i].p;
        -: 4518:				     q_arr[i] = (v < q1) ? v : q1;
        -: 4519:				 }
        -: 4520:				 // q[i2] <- q[n - j]
        -: 4521:				 for (size_t i = 0; i < i2_len; i++) {
        -: 4522:				     q_arr[n_mj + 1 + i] = q_arr[n_mj];
        -: 4523:				}
        -: 4524:				 // pa <- pmax(pa, q)
        -: 4525:				for (size_t i = 0; i < n; i++) {
        -: 4526:				    if (pa[i] < q_arr[i]) {
        -: 4527:				       pa[i] = q_arr[i];
       15: 4528:				    }
branch  0 taken 6 (fallthrough)
branch  1 taken 9
        6: 4528-block  0
        9: 4528-block  1
call    2 returned 9
       15: 4529:				}
        -: 4530:			}
       15: 4531:			// pmin(1, pmax(pa, p))[ro] — map sorted results back to original indices
       15: 4531-block  0
        -: 4532:			for (size_t i = 0; i < n; i++) {
        -: 4533:				double v = (pa[i] > arr[i].p) ? pa[i] : arr[i].p;
        -: 4534:				if (v > 1.0) v = 1.0;
        -: 4535:				adj[arr[i].orig_idx] = v;
function XS_Stats__LikeR_cor called 18 returned 67% blocks executed 75%
       18: 4536:			}
        -: 4537:			Safefree(pa);  Safefree(q_arr);
       18: 4538:		} else if (strcmp(meth, "none") == 0) {
       18: 4538-block  0
call    0 returned 18
       18: 4539:			for (size_t i = 0; i < n; i++) {
branch  0 taken 18 (fallthrough)
branch  1 taken 0
       18: 4539-block  0
branch  2 taken 0 (fallthrough)
branch  3 taken 18
    #####: 4540:				adj[arr[i].orig_idx] = arr[i].p;
    %%%%%: 4540-block  0
call    0 never executed
        -: 4541:			}
        -: 4542:		} else {
       18: 4543:			Safefree(arr); Safefree(adj);
        -: 4544:			croak("Unknown p-value adjustment method: %s", method);
        -: 4545:		}
        -: 4546:		// Push values onto the Perl stack as a flat list
        -: 4547:		EXTEND(SP, n);
       18: 4548:		for (size_t i = 0; i < n; i++) {
       18: 4548-block  0
branch  0 taken 0 (fallthrough)
branch  1 taken 18
    #####: 4549:			PUSHs(sv_2mortal(newSVnv(adj[i])));
    %%%%%: 4549-block  0
        -: 4550:		}
       18: 4551:		Safefree(arr); arr = NULL;
       18: 4551-block  0
        -: 4552:		Safefree(adj); adj = NULL;
        -: 4553:#line 4554 "LikeR.c"
        -: 4554:	PUTBACK;
       18: 4555:	return;
       18: 4555-block  0
branch  0 taken 3 (fallthrough)
branch  1 taken 15
        3: 4556:    }
        3: 4556-block  0
        -: 4557:}
       15: 4558:
       15: 4558-block  0
call    0 returned 15
        -: 4559:
        -: 4560:XS_EUPXS(XS_Stats__LikeR_median); /* prototype to pass -Wmissing-prototypes */
        -: 4561:XS_EUPXS(XS_Stats__LikeR_median)
        -: 4562:{
        -: 4563:    dVAR; dXSARGS;
        -: 4564:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4565:    PERL_UNUSED_VAR(items); /* -W */
        -: 4566:    {
        -: 4567:	double	RETVAL;
        -: 4568:	dXSTARG;
        -: 4569:#line 4102 "LikeR.xs"
        -: 4570:	  size_t total_count = 0, k = 0;
        -: 4571:	  double* restrict nums;
        -: 4572:	  double median_val = 0.0;
        -: 4573:#line 4574 "LikeR.c"
        -: 4574:#line 4106 "LikeR.xs"
        -: 4575:	  /* Pass 1: Count valid elements — die immediately on any undef */
        -: 4576:	  for (size_t i = 0; i < items; i++) {
        -: 4577:		   SV* restrict arg = ST(i);
        -: 4578:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4579:		       AV* restrict av = (AV*)SvRV(arg);
        -: 4580:		       size_t len = av_len(av) + 1;
        -: 4581:		       for (size_t j = 0; j < len; j++) {
        -: 4582:		           SV** restrict tv = av_fetch(av, j, 0);
        -: 4583:		           if (tv && SvOK(*tv)) {
        -: 4584:		               total_count++;
        -: 4585:		           } else {
        -: 4586:		               croak("median: undefined value at array ref index %zu (argument %zu)", j, i);
        -: 4587:		           }
        -: 4588:		       }
        -: 4589:		   } else if (SvOK(arg)) {
        -: 4590:		       total_count++;
        -: 4591:		   } else {
        -: 4592:		       croak("median: undefined value at argument index %zu", i);
        -: 4593:		   }
        -: 4594:	  }
        -: 4595:	  if (total_count == 0) croak("median needs >= 1 element");
        -: 4596:
        -: 4597:	  /* Allocate C array now that we know the exact size */
        -: 4598:	  Newx(nums, total_count, double);
        -: 4599:
        -: 4600:	  /* Pass 2: Populate the C array — Safefree before any croak */
        -: 4601:	  for (size_t i = 0; i < items; i++) {
        -: 4602:		   SV* restrict arg = ST(i);
        -: 4603:		   if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 4604:		       AV* restrict av = (AV*)SvRV(arg);
        -: 4605:		       size_t len = av_len(av) + 1;
        -: 4606:		       for (size_t j = 0; j < len; j++) {
        -: 4607:		           SV** restrict tv = av_fetch(av, j, 0);
        -: 4608:		           if (tv && SvOK(*tv)) {
        -: 4609:		               nums[k++] = SvNV(*tv);
        -: 4610:		           } else {
        -: 4611:		               Safefree(nums);
        -: 4612:		               croak("median: undefined value at array ref index %zu (argument %zu)", j, i);
        -: 4613:		           }
        -: 4614:		       }
        -: 4615:		   } else if (SvOK(arg)) {
        -: 4616:		       nums[k++] = SvNV(arg);
        -: 4617:		   } else {
        -: 4618:		       Safefree(nums);
        -: 4619:		       croak("median: undefined value at argument index %zu", i);
        -: 4620:		   }
        -: 4621:	  }
        -: 4622:
        -: 4623:	  /* Sort and calculate median */
        -: 4624:	  qsort(nums, total_count, sizeof(double), compare_doubles);
        -: 4625:	  if (total_count % 2 == 0) {
        -: 4626:		   median_val = (nums[total_count / 2 - 1] + nums[total_count / 2]) / 2.0;
        -: 4627:	  } else {
        -: 4628:		   median_val = nums[total_count / 2];
        -: 4629:	  }
        -: 4630:	  Safefree(nums);
        -: 4631:	  nums = NULL;
        -: 4632:	  RETVAL = median_val;
        -: 4633:#line 4634 "LikeR.c"
        -: 4634:	TARGn((double)RETVAL, 1);
        -: 4635:	ST(0) = TARG;
        -: 4636:    }
        -: 4637:    XSRETURN(1);
        -: 4638:}
        -: 4639:
        -: 4640:
        -: 4641:XS_EUPXS(XS_Stats__LikeR_cor); /* prototype to pass -Wmissing-prototypes */
        -: 4642:XS_EUPXS(XS_Stats__LikeR_cor)
        -: 4643:{
        -: 4644:    dVAR; dXSARGS;
        -: 4645:    if (items < 1 || items > 3)
        -: 4646:       croak_xs_usage(cv,  "x_sv, y_sv= &PL_sv_undef, method= \"pearson\"");
        -: 4647:    {
        -: 4648:	SV *	RETVAL;
        -: 4649:	SV *	x_sv = ST(0)
        -: 4650:;
        -: 4651:	SV *	y_sv;
        -: 4652:	const char *	method;
        -: 4653:
        -: 4654:	if (items < 2)
        -: 4655:	    y_sv = &PL_sv_undef;
        -: 4656:	else {
        -: 4657:	    y_sv = ST(1)
        -: 4658:;
        -: 4659:	}
        -: 4660:
        -: 4661:	if (items < 3)
        -: 4662:	    method = "pearson";
        -: 4663:	else {
        -: 4664:	    method = (const char *)SvPV_nolen(ST(2))
        -: 4665:;
        -: 4666:	}
        -: 4667:#line 4169 "LikeR.xs"
        -: 4668:	  // --- validate method -------------------------------------------
        -: 4669:	  if (strcmp(method, "pearson")  != 0 &&
        -: 4670:		   strcmp(method, "spearman") != 0 &&
        -: 4671:		   strcmp(method, "kendall")  != 0)
        -: 4672:		   croak("cor: unknown method '%s' (use 'pearson', 'spearman', or 'kendall')",
        -: 4673:		         method);
        -: 4674:
        -: 4675:	  // --- validate x ------------------------------------------------
        -: 4676:	  if (!SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 4677:		   croak("cor: x must be an ARRAY reference");
        -: 4678:
        -: 4679:	  AV*restrict x_av = (AV*)SvRV(x_sv);
        -: 4680:	  size_t nx   = av_len(x_av) + 1;
        -: 4681:	  if (nx == 0) croak("cor: x is empty");
        -: 4682:
        -: 4683:	  // --- detect whether x is a flat vector or a matrix (AoA) -------
        -: 4684:	  bool x_is_matrix = 0;
        -: 4685:	  {
        -: 4686:		   SV**restrict fp = av_fetch(x_av, 0, 0);
        -: 4687:		   if (fp && SvROK(*fp) && SvTYPE(SvRV(*fp)) == SVt_PVAV)
        -: 4688:		       x_is_matrix = 1;
        -: 4689:	  }
        -: 4690:
        -: 4691:	  // --- detect y ----------------------------
        -: 4692:	  bool has_y = (SvOK(y_sv) && SvROK(y_sv) &&
        -: 4693:		            SvTYPE(SvRV(y_sv)) == SVt_PVAV);
        -: 4694:
        -: 4695:	  AV*restrict y_av = has_y ? (AV*)SvRV(y_sv) : NULL;
        -: 4696:	  size_t ny = has_y ? av_len(y_av) + 1 : 0;
        -: 4697:
        -: 4698:	  bool y_is_matrix = 0;
        -: 4699:	  if (has_y && ny > 0) {
        -: 4700:		   SV**restrict fp = av_fetch(y_av, 0, 0);
        -: 4701:		   if (fp && SvROK(*fp) && SvTYPE(SvRV(*fp)) == SVt_PVAV)
        -: 4702:		       y_is_matrix = 1;
        -: 4703:	  }
        -: 4704:
        -: 4705:#line 4706 "LikeR.c"
        -: 4706:#line 4207 "LikeR.xs"
        -: 4707:      // Branch 1: both inputs are flat vectors  →  scalar result
        -: 4708:	  if (!x_is_matrix && !y_is_matrix) {
        -: 4709:		   if (!has_y) {
        -: 4710:		       /* cor(vector) == 1 by definition */
        -: 4711:		       RETVAL = newSVnv(1.0);
        -: 4712:		   } else {
        -: 4713:		       if (nx != ny)
        -: 4714:		           croak("cor: x and y must have the same length (%lu vs %lu)",
        -: 4715:		                 nx, ny);
        -: 4716:
        -: 4717:		       if (nx < 2)
        -: 4718:		           croak("cor: need at least 2 observations");
        -: 4719:
        -: 4720:		       double *restrict xd, *restrict yd;
        -: 4721:		       Newx(xd, nx, double);
        -: 4722:		       Newx(yd, ny, double);
        -: 4723:
        -: 4724:		       for (size_t i = 0; i < nx; i++) {
        -: 4725:		           SV**restrict tv = av_fetch(x_av, i, 0);
        -: 4726:		           xd[i] = (tv && SvOK(*tv) && looks_like_number(*tv)) ? SvNV(*tv) : NAN;
        -: 4727:		       }
        -: 4728:		       for (size_t i = 0; i < ny; i++) {
        -: 4729:		           SV**restrict tv = av_fetch(y_av, i, 0);
        -: 4730:		           yd[i] = (tv && SvOK(*tv) && looks_like_number(*tv)) ? SvNV(*tv) : NAN;
        -: 4731:		       }
        -: 4732:
        -: 4733:		       double r = compute_cor(xd, yd, nx, method);
        -: 4734:		       Safefree(xd); Safefree(yd);
        -: 4735:		       RETVAL = newSVnv(r);
        -: 4736:		   }
        -: 4737:	  } else {//Branch 2: x is a matrix (or y is a matrix)  →  AoA result
        -: 4738:		   // -- resolve x matrix dimensions
        -: 4739:		   if (!x_is_matrix)
        -: 4740:		       croak("cor: x must be a matrix (array ref of array refs) "
        -: 4741:		             "when y is a matrix");
        -: 4742:
        -: 4743:		   SV**restrict xr0 = av_fetch(x_av, 0, 0);
        -: 4744:		   if (!xr0 || !SvROK(*xr0) || SvTYPE(SvRV(*xr0)) != SVt_PVAV)
        -: 4745:		       croak("cor: each row of x must be an ARRAY reference");
        -: 4746:
        -: 4747:		   size_t ncols_x = av_len((AV*)SvRV(*xr0)) + 1;
        -: 4748:		   if (ncols_x == 0) croak("cor: x matrix has zero columns");
        -: 4749:
        -: 4750:		   size_t nrows   = nx;   /* observations */
        -: 4751:
        -: 4752:		   // PRE-VALIDATION PASS: Ensure all rows are arrays to prevent memory leaks on croak
        -: 4753:		   for (size_t i = 0; i < nrows; i++) {
        -: 4754:		       SV**restrict rv = av_fetch(x_av, i, 0);
        -: 4755:		       if (!rv || !SvROK(*rv) || SvTYPE(SvRV(*rv)) != SVt_PVAV)
        -: 4756:		           croak("cor: x row %lu is not an array ref", i);
       12: 4757:		   }
       12: 4757-block  0
call    0 returned 12
       12: 4758:
        -: 4759:           if (has_y && y_is_matrix) {
       12: 4760:		       if (ny != nrows) croak("cor: x and y must have the same number of rows (%lu vs %lu)", nrows, ny);
        -: 4761:               for (size_t i = 0; i < nrows; i++) {
        -: 4762:		           SV**restrict rv = av_fetch(y_av, i, 0);
        -: 4763:		           if (!rv || !SvROK(*rv) || SvTYPE(SvRV(*rv)) != SVt_PVAV)
        -: 4764:		               croak("cor: y row %lu is not an array ref", i);
function XS_Stats__LikeR_scale called 15 returned 80% blocks executed 71%
       15: 4765:		       }
        -: 4766:           }
       15: 4767:
       15: 4767-block  0
call    0 returned 15
        -: 4768:		   // -- extract x columns
        -: 4769:		   double **restrict col_x;
        -: 4770:		   Newx(col_x, ncols_x, double*);
       15: 4771:
branch  0 taken 15 (fallthrough)
branch  1 taken 0
        -: 4772:		   for (size_t j = 0; j < ncols_x; j++) {
        -: 4773:		       Newx(col_x[j], nrows, double);
        -: 4774:		       for (size_t i = 0; i < nrows; i++) {
        -: 4775:		           SV**restrict rv = av_fetch(x_av, i, 0);
        -: 4776:		           AV*restrict  row = (AV*)SvRV(*rv);
        -: 4777:		           SV**restrict cv  = av_fetch(row, j, 0);
        -: 4778:		           col_x[j][i] = (cv && SvOK(*cv) && looks_like_number(*cv)) ? SvNV(*cv) : NAN;
        -: 4779:		       }
        -: 4780:		   }
        -: 4781:
        -: 4782:		   // -- resolve y: separate matrix or re-use x (symmetric)
        -: 4783:		   size_t ncols_y;
        -: 4784:		   double **restrict col_y   = NULL;
        -: 4785:		   bool symmetric = 0;
        -: 4786:
        -: 4787:		   // 1 = cor(X) — result is symmetric
        -: 4788:		   if (has_y && y_is_matrix) {
        -: 4789:		       // cross-correlation: X (nrows × p) vs Y (nrows × q)
        -: 4790:		       SV**restrict yr0 = av_fetch(y_av, 0, 0);
        -: 4791:		       ncols_y = av_len((AV*)SvRV(*yr0)) + 1;
        -: 4792:		       if (ncols_y == 0) croak("cor: y matrix has zero columns");
        -: 4793:
        -: 4794:		       Newx(col_y, ncols_y, double*);
        -: 4795:		       for (size_t j = 0; j < ncols_y; j++) {
        -: 4796:		           Newx(col_y[j], nrows, double);
        -: 4797:		           for (size_t i = 0; i < nrows; i++) {
        -: 4798:		               SV**restrict  rv = av_fetch(y_av, i, 0);
        -: 4799:		               AV*restrict  row = (AV*)SvRV(*rv);
        -: 4800:		               SV**restrict cv  = av_fetch(row, j, 0);
        -: 4801:		               col_y[j][i] = (cv && SvOK(*cv) && looks_like_number(*cv)) ? SvNV(*cv) : NAN;
        -: 4802:		           }
        -: 4803:		       }
        -: 4804:		   } else { // cor(X) — symmetric p×p result; share column arrays
        -: 4805:		       ncols_y  = ncols_x;
        -: 4806:		       col_y    = col_x;
        -: 4807:		       symmetric = 1;
        -: 4808:		   }
        -: 4809:		   if (nrows < 2)
        -: 4810:		       croak("cor: need at least 2 observations (got %lu)", nrows);
        -: 4811:		   // -- build cache for symmetric case: compute upper triangle, store results, mirror to lower triangle
        -: 4812:		   AV*restrict result_av = newAV();
        -: 4813:		   av_extend(result_av, ncols_x - 1);
        -: 4814:		   // Allocate per-row AVs up front so we can fill them in order
        -: 4815:		   AV **restrict rows_out;
        -: 4816:		   Newx(rows_out, ncols_x, AV*);
        -: 4817:		   for (size_t i = 0; i < ncols_x; i++) {
        -: 4818:		       rows_out[i] = newAV();
        -: 4819:		       av_extend(rows_out[i], ncols_y - 1);
        -: 4820:		   }
        -: 4821:		   if (symmetric) {
        -: 4822:		       /* Upper triangle + diagonal, then mirror. r_cache[i][j] (j >= i) holds the computed value. */
        -: 4823:		       double **restrict r_cache;
        -: 4824:		       Newx(r_cache, ncols_x, double*);
        -: 4825:		       for (size_t i = 0; i < ncols_x; i++)
        -: 4826:		           Newx(r_cache[i], ncols_x, double);
        -: 4827:
        -: 4828:		       for (size_t i = 0; i < ncols_x; i++) {
        -: 4829:		           r_cache[i][i] = 1.0; // diagonal
        -: 4830:		           for (size_t j = i + 1; j < ncols_x; j++) {
        -: 4831:		               double r = compute_cor(col_x[i], col_x[j], nrows, method);
        -: 4832:		               r_cache[i][j] = r;
        -: 4833:		               r_cache[j][i] = r; // symmetry
        -: 4834:		           }
        -: 4835:		       }
        -: 4836:		       // fill output AoA from cache
        -: 4837:		       for (size_t i = 0; i < ncols_x; i++)
        -: 4838:		           for (size_t j = 0; j < ncols_x; j++)
        -: 4839:		               av_store(rows_out[i], j, newSVnv(r_cache[i][j]));
        -: 4840:
        -: 4841:		       for (size_t i = 0; i < ncols_x; i++) Safefree(r_cache[i]);
        -: 4842:		       Safefree(r_cache); r_cache = NULL;
        -: 4843:		   } else {
        -: 4844:		       // cross-correlation: every (i,j) pair is independent
        -: 4845:		       for (size_t i = 0; i < ncols_x; i++)
        -: 4846:		           for (size_t j = 0; j < ncols_y; j++)
        -: 4847:		               av_store(rows_out[i], j, newSVnv(compute_cor(col_x[i], col_y[j], nrows, method)));
        -: 4848:		   }
        -: 4849:		   // push row AVs into result
        -: 4850:		   for (size_t i = 0; i < ncols_x; i++)
        -: 4851:		       av_store(result_av, i, newRV_noinc((SV*)rows_out[i]));
        -: 4852:		   Safefree(rows_out); rows_out = NULL;
        -: 4853:		   // -- free column arrays -------------------------------------
        -: 4854:		   for (size_t j = 0; j < ncols_x; j++) Safefree(col_x[j]);
        -: 4855:		   Safefree(col_x); col_x = NULL;
        -: 4856:		   if (!symmetric) {
        -: 4857:		       for (size_t j = 0; j < ncols_y; j++) Safefree(col_y[j]);
        -: 4858:		       Safefree(col_y);
        -: 4859:		   }
        -: 4860:		   RETVAL = newRV_noinc((SV*)result_av);
        -: 4861:	  }
        -: 4862:#line 4863 "LikeR.c"
        -: 4863:	RETVAL = sv_2mortal(RETVAL);
        -: 4864:	ST(0) = RETVAL;
        -: 4865:    }
        -: 4866:    XSRETURN(1);
        -: 4867:}
        -: 4868:
        -: 4869:
        -: 4870:XS_EUPXS(XS_Stats__LikeR_scale); /* prototype to pass -Wmissing-prototypes */
        -: 4871:XS_EUPXS(XS_Stats__LikeR_scale)
        -: 4872:{
        -: 4873:    dVAR; dXSARGS;
        -: 4874:    PERL_UNUSED_VAR(cv); /* -W */
        -: 4875:    PERL_UNUSED_VAR(items); /* -W */
        -: 4876:    PERL_UNUSED_VAR(ax); /* -Wall */
        -: 4877:    SP -= items;
        -: 4878:    {
        -: 4879:#line 4368 "LikeR.xs"
        -: 4880:	{
        -: 4881:		bool do_center_mean = true, do_scale_sd = true;
        -: 4882:		double center_val = 0.0, scale_val = 1.0;
        -: 4883:		size_t data_items = items;
        -: 4884:		// 1. Parse Options Hash (if it exists as the last argument)
        -: 4885:		if (items > 0) {
        -: 4886:			SV*restrict last_arg = ST(items - 1);
        -: 4887:			if (SvROK(last_arg) && SvTYPE(SvRV(last_arg)) == SVt_PVHV) {
        -: 4888:				 data_items = items - 1; // Exclude hash from data processing
        -: 4889:				 HV*restrict opt_hv = (HV*)SvRV(last_arg);
        -: 4890:				 // --- Parse 'center'
        -: 4891:				 SV**restrict center_sv = hv_fetch(opt_hv, "center", 6, 0);
        -: 4892:				 if (center_sv) {
        -: 4893:				     SV*restrict val_sv = *center_sv;
        -: 4894:				     if (!SvOK(val_sv)) {
        -: 4895:				         do_center_mean = false; center_val = 0.0;
        -: 4896:				     } else {
        -: 4897:				         char *restrict str = SvPV_nolen(val_sv);
        -: 4898:				         /* Trap booleans and empty strings before numeric checks */
        -: 4899:				         if (strcasecmp(str, "mean") == 0 || strcasecmp(str, "true") == 0 || strcmp(str, "1") == 0) {
        -: 4900:				             do_center_mean = true;
        -: 4901:				         } else if (strcasecmp(str, "none") == 0 || strcasecmp(str, "false") == 0 || strcmp(str, "0") == 0 || strcmp(str, "") == 0) {
        -: 4902:				             do_center_mean = false; center_val = 0.0;
        -: 4903:				         } else if (looks_like_number(val_sv)) {
        -: 4904:				             do_center_mean = false; center_val = SvNV(val_sv);
        -: 4905:				         } else if (SvTRUE(val_sv)) {
        -: 4906:				             do_center_mean = true;
        -: 4907:				         } else {
        -: 4908:				             do_center_mean = false; center_val = 0.0;
        -: 4909:				         }
        -: 4910:				     }
        -: 4911:				 }
        -: 4912:				 // --- Parse 'scale' ---
        -: 4913:				 SV**restrict scale_sv = hv_fetch(opt_hv, "scale", 5, 0);
        -: 4914:				 if (scale_sv) {
        -: 4915:				     SV*restrict val_sv = *scale_sv;
        -: 4916:				     if (!SvOK(val_sv)) {
        -: 4917:				         do_scale_sd = false; scale_val = 1.0;
        -: 4918:				     } else {
        -: 4919:				         char *restrict str = SvPV_nolen(val_sv);
        -: 4920:				         if (strcasecmp(str, "sd") == 0 || strcasecmp(str, "true") == 0 || strcmp(str, "1") == 0) {
        -: 4921:				             do_scale_sd = true;
        -: 4922:				         } else if (strcasecmp(str, "none") == 0 || strcasecmp(str, "false") == 0 || strcmp(str, "0") == 0 || strcmp(str, "") == 0) {
        -: 4923:				             do_scale_sd = false; scale_val = 1.0;
        -: 4924:				         } else if (looks_like_number(val_sv)) {
        -: 4925:				             do_scale_sd = false; scale_val = SvNV(val_sv);
        -: 4926:				             if (scale_val == 0.0) scale_val = 1.0; /* Prevent Division By Zero */
        -: 4927:				         } else if (SvTRUE(val_sv)) {
        -: 4928:				             do_scale_sd = true;
        -: 4929:				         } else {
        -: 4930:				             do_scale_sd = false; scale_val = 1.0;
        -: 4931:				         }
        -: 4932:				     }
        -: 4933:				 }
        -: 4934:			}
        -: 4935:		}
        -: 4936:		// 2. Detect if the input is a Matrix (Array of Arrays)
        -: 4937:		bool is_matrix = false;
        -: 4938:		if (data_items == 1) {
        -: 4939:			SV*restrict first_arg = ST(0);
        -: 4940:			if (SvROK(first_arg) && SvTYPE(SvRV(first_arg)) == SVt_PVAV) {
        -: 4941:				 AV*restrict av = (AV*)SvRV(first_arg);
        -: 4942:				 if (av_len(av) >= 0) {
        -: 4943:				     SV**restrict first_elem = av_fetch(av, 0, 0);
        -: 4944:				     if (first_elem && SvROK(*first_elem) && SvTYPE(SvRV(*first_elem)) == SVt_PVAV) {
        -: 4945:				         is_matrix = true;
        -: 4946:				     }
        -: 4947:				 }
        -: 4948:			}
        -: 4949:		}
        -: 4950:		if (is_matrix) {
        -: 4951:			//=========================================================
        -: 4952:			// MATRIX MODE: Scale columns independently (Just like R)
        -: 4953:			//=========================================================
        -: 4954:			AV*restrict mat_av = (AV*)SvRV(ST(0));
        -: 4955:			size_t nrow = av_len(mat_av) + 1, ncol = 0;
        -: 4956:
        -: 4957:			SV**restrict first_row = av_fetch(mat_av, 0, 0);
        -: 4958:			ncol = av_len((AV*)SvRV(*first_row)) + 1;
        -: 4959:
        -: 4960:			if (nrow == 0 || ncol == 0) croak("scale requires non-empty matrix");
        -: 4961:
        -: 4962:			// Create a new matrix for the scaled output
        -: 4963:			AV*restrict result_av = newAV();
        -: 4964:			av_extend(result_av, nrow - 1);
        -: 4965:			AV**restrict row_ptrs = (AV**)safemalloc(nrow * sizeof(AV*));
        -: 4966:			for (size_t r = 0; r < nrow; r++) {
        -: 4967:				 row_ptrs[r] = newAV();
        -: 4968:				 av_extend(row_ptrs[r], ncol - 1);
        -: 4969:				 av_push(result_av, newRV_noinc((SV*)row_ptrs[r]));
        -: 4970:			}
        -: 4971:			// Calculate and apply scale per column
        -: 4972:			for (size_t c = 0; c < ncol; c++) {
        -: 4973:				 double col_sum = 0.0;
        -: 4974:				 double *restrict col_data;
       12: 4975:				 Newx(col_data, nrow, double);
       12: 4976:				 // Extract the column data
       12: 4976-block  0
        -: 4977:				 for (size_t r = 0; r < nrow; r++) {
        -: 4978:				     SV**restrict row_sv = av_fetch(mat_av, r, 0);
        -: 4979:				     if (row_sv && SvROK(*row_sv)) {
        -: 4980:				         AV*restrict row_av = (AV*)SvRV(*row_sv);
        -: 4981:				         SV**restrict cell_sv = av_fetch(row_av, c, 0);
function XS_Stats__LikeR_matrix called 12 returned 25% blocks executed 82%
       12: 4982:				         col_data[r] = (cell_sv && SvOK(*cell_sv)) ? SvNV(*cell_sv) : 0.0;
        -: 4983:				     } else {
       12: 4984:				         col_data[r] = 0.0;
       12: 4984-block  0
call    0 returned 12
branch  1 taken 0 (fallthrough)
branch  2 taken 12
        -: 4985:				     }
        -: 4986:				     col_sum += col_data[r];
        -: 4987:				 }
        -: 4988:
        -: 4989:				 double col_center = do_center_mean ? (col_sum / nrow) : center_val;
        -: 4990:				 double col_scale = scale_val;
        -: 4991:				 // Calculate Standard Deviation for this specific column if needed
        -: 4992:				 if (do_scale_sd) {
        -: 4993:				     if (nrow <= 1) {
        -: 4994:				         Safefree(col_data);
        -: 4995:				         safefree(row_ptrs);
        -: 4996:				         croak("scale needs >= 2 rows to calculate standard deviation for a matrix column");
        -: 4997:				     }
        -: 4998:				     double sum_sq = 0.0;
        -: 4999:				     for (size_t r = 0; r < nrow; r++) {
        -: 5000:				         double diff = col_data[r] - col_center;
        -: 5001:				         sum_sq += diff * diff;
        -: 5002:				     }
        -: 5003:				     col_scale = sqrt(sum_sq / (nrow - 1));
        -: 5004:				 }
        -: 5005:				 // Store scaled values back into the new matrix rows
        -: 5006:				 for (size_t r = 0; r < nrow; r++) {
        -: 5007:				     double centered = col_data[r] - col_center;
        -: 5008:				     double final_val = (col_scale == 0.0) ? (0.0 / 0.0) : (centered / col_scale);
        -: 5009:				     av_store(row_ptrs[r], c, newSVnv(final_val));
        -: 5010:				 }
        -: 5011:				 Safefree(col_data);
        -: 5012:			}
        -: 5013:			safefree(row_ptrs);
        -: 5014:			// Push the resulting matrix as a single Reference onto the Perl stack
        -: 5015:			EXTEND(SP, 1);
        -: 5016:			PUSHs(sv_2mortal(newRV_noinc((SV*)result_av)));
        -: 5017:		} else {
        -: 5018:			// ======================================
        -: 5019:			// FLAT LIST MODE: Original functionality
        -: 5020:			// ======================================
        -: 5021:			size_t total_count = 0, k = 0;
        -: 5022:			double *restrict nums;
        -: 5023:			double sum = 0.0;
        -: 5024:			for (size_t i = 0; i < data_items; i++) {
        -: 5025:				SV*restrict arg = ST(i);
        -: 5026:				if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 5027:					AV*restrict av = (AV*)SvRV(arg);
        -: 5028:					size_t len = av_len(av) + 1;
        -: 5029:					for (unsigned int j = 0; j < len; j++) {
        -: 5030:						SV**restrict tv = av_fetch(av, j, 0);
        -: 5031:						if (tv && SvOK(*tv)) { total_count++; }
        -: 5032:					}
        -: 5033:				} else if (SvOK(arg)) {
        -: 5034:					total_count++;
        -: 5035:				}
        -: 5036:			}
        -: 5037:			if (total_count == 0) croak("scale requires at least 1 numeric element");
        -: 5038:			Newx(nums, total_count, double);
        -: 5039:			for (size_t i = 0; i < data_items; i++) {
        -: 5040:				 SV*restrict arg = ST(i);
        -: 5041:				 if (SvROK(arg) && SvTYPE(SvRV(arg)) == SVt_PVAV) {
        -: 5042:				     AV*restrict av = (AV*)SvRV(arg);
        -: 5043:				     size_t len = av_len(av) + 1;
        -: 5044:				     for (size_t j = 0; j < len; j++) {
        -: 5045:				         SV**restrict tv = av_fetch(av, j, 0);
        -: 5046:				         if (tv && SvOK(*tv)) { 
        -: 5047:				             double val = SvNV(*tv);
        -: 5048:				             nums[k++] = val; sum += val;
        -: 5049:				         }
        -: 5050:				     }
        -: 5051:				 } else if (SvOK(arg)) {
        -: 5052:				     double val = SvNV(arg);
        -: 5053:				     nums[k++] = val; sum += val;
        -: 5054:				 }
        -: 5055:			}
        -: 5056:			if (do_center_mean) center_val = sum / total_count;
        -: 5057:			if (do_scale_sd) {
        -: 5058:				 if (total_count <= 1) {
        -: 5059:				     Safefree(nums);
        -: 5060:				     croak("scale needs >= 2 elements to calculate SD");
        -: 5061:				 }
        -: 5062:				 double sum_sq = 0.0;
        -: 5063:				 for (size_t i = 0; i < total_count; i++) {
        -: 5064:				     double diff = nums[i] - center_val;
        3: 5065:				     sum_sq += diff * diff;
call    0 returned 3
        3: 5066:				 }
        -: 5067:				 scale_val = sqrt(sum_sq / (total_count - 1));
        3: 5068:			}
        -: 5069:			EXTEND(SP, total_count);
        -: 5070:			for (size_t i = 0; i < total_count; i++) {
        -: 5071:				double centered = nums[i] - center_val;
        -: 5072:				double final_val = (scale_val == 0.0) ? (0.0 / 0.0) : (centered / scale_val);
function XS_Stats__LikeR_lm called 66 returned 73% blocks executed 80%
       66: 5073:				PUSHs(sv_2mortal(newSVnv(final_val)));
        -: 5074:			}
       66: 5075:			Safefree(nums); nums = NULL;
       66: 5075-block  0
call    0 returned 66
branch  1 taken 0 (fallthrough)
branch  2 taken 66
        -: 5076:		}
        -: 5077:	}
        -: 5078:#line 5079 "LikeR.c"
        -: 5079:	PUTBACK;
        -: 5080:	return;
        -: 5081:    }
        -: 5082:}
        -: 5083:
        -: 5084:
        -: 5085:XS_EUPXS(XS_Stats__LikeR_matrix); /* prototype to pass -Wmissing-prototypes */
        -: 5086:XS_EUPXS(XS_Stats__LikeR_matrix)
        -: 5087:{
        -: 5088:    dVAR; dXSARGS;
        -: 5089:    PERL_UNUSED_VAR(cv); /* -W */
        -: 5090:    PERL_UNUSED_VAR(items); /* -W */
        -: 5091:    {
        -: 5092:	SV *	RETVAL;
        -: 5093:#line 4569 "LikeR.xs"
        -: 5094:	// Basic check: must have an even number of arguments for key => value
        -: 5095:	if (items % 2 != 0) {
        -: 5096:	  croak("Usage: matrix(data => [...], nrow => $n, ncol => $m, byrow => $bool)");
        -: 5097:	}
        -: 5098:	SV*restrict data_sv = NULL;
        -: 5099:	size_t nrow = 0, ncol = 0;
        -: 5100:	bool byrow = FALSE, nrow_set = FALSE, ncol_set = FALSE;
        -: 5101:	// Parse named arguments
        -: 5102:	for (size_t i = 0; i < items; i += 2) {
        -: 5103:	  char*restrict key = SvPV_nolen(ST(i));
        -: 5104:	  SV*restrict val   = ST(i + 1);
        -: 5105:	  if (strEQ(key, "data")) {
        -: 5106:		   data_sv = val;
        -: 5107:	  } else if (strEQ(key, "nrow")) {
        -: 5108:		   nrow = (size_t)SvUV(val);
        -: 5109:		   nrow_set = TRUE;
        -: 5110:	  } else if (strEQ(key, "ncol")) {
        -: 5111:		   ncol = (size_t)SvUV(val);
        -: 5112:		   ncol_set = TRUE;
        -: 5113:	  } else if (strEQ(key, "byrow")) {
        -: 5114:		   byrow = SvTRUE(val);
        -: 5115:	  } else {
        -: 5116:		   croak("Unknown option: %s", key);
        -: 5117:	  }
        -: 5118:	}
        -: 5119:	// Validate data input
        -: 5120:	if (!data_sv || !SvROK(data_sv) || SvTYPE(SvRV(data_sv)) != SVt_PVAV) {
        -: 5121:	  croak("The 'data' option must be an array reference (e.g. data => [1..6])");
        -: 5122:	}
        -: 5123:	AV*restrict data_av = (AV*)SvRV(data_sv);
        -: 5124:	size_t data_len = (UV)(av_top_index(data_av) + 1);
        -: 5125:	if (data_len == 0) {
        -: 5126:	  croak("Data array cannot be empty");
        -: 5127:	}
        -: 5128:	// R-style dimension inference
        -: 5129:	if (!nrow_set && !ncol_set) {
        -: 5130:	  nrow = data_len;
        -: 5131:	  ncol = 1;
        -: 5132:	} else if (nrow_set && !ncol_set) {
        -: 5133:	  ncol = (data_len + nrow - 1) / nrow;
        -: 5134:	} else if (!nrow_set && ncol_set) {
        -: 5135:	  nrow = (data_len + ncol - 1) / ncol;
        -: 5136:	}
        -: 5137:	// Final safety check for dimensions
        -: 5138:	if (nrow == 0 || ncol == 0) {
        -: 5139:	  croak("Dimensions must be greater than 0");
        -: 5140:	}
        -: 5141:	// Create the matrix (Array of Arrays)
        -: 5142:	AV*restrict result_av = newAV();
        -: 5143:	av_extend(result_av, nrow - 1);
        -: 5144:	size_t r, c;// Use unsigned types for counters to prevent negative indexing
        -: 5145:	AV**restrict row_ptrs = (AV**restrict)safemalloc(nrow * sizeof(AV*)); /* Pre-allocate row pointers */
        -: 5146:	for (r = 0; r < nrow; r++) {
        -: 5147:	  row_ptrs[r] = newAV();
        -: 5148:	  av_extend(row_ptrs[r], ncol - 1);
        -: 5149:	  av_push(result_av, newRV_noinc((SV*)row_ptrs[r]));
        -: 5150:	}
        -: 5151:	// Fill the matrix
        -: 5152:	size_t total_cells = nrow * ncol;
        -: 5153:	for (size_t i = 0; i < total_cells; i++) {
        -: 5154:	  // Vector recycling logic
        -: 5155:	  SV**restrict fetched = av_fetch(data_av, i % data_len, 0);
        -: 5156:	  SV*restrict val = fetched ? newSVsv(*fetched) : newSV(0);
        -: 5157:	  if (byrow) {
        -: 5158:		   r = i / ncol;
        -: 5159:		   c = i % ncol;
        -: 5160:	  } else {
        -: 5161:		   r = i % nrow;
        -: 5162:		   c = i / nrow;
        -: 5163:	  }
        -: 5164:	  av_store(row_ptrs[r], c, val);
        -: 5165:	}
        -: 5166:	safefree(row_ptrs);
        -: 5167:	RETVAL = newRV_noinc((SV*)result_av);
        -: 5168:#line 5169 "LikeR.c"
        -: 5169:	RETVAL = sv_2mortal(RETVAL);
        -: 5170:	ST(0) = RETVAL;
        -: 5171:    }
        -: 5172:    XSRETURN(1);
        -: 5173:}
        -: 5174:
        -: 5175:
        -: 5176:XS_EUPXS(XS_Stats__LikeR_lm); /* prototype to pass -Wmissing-prototypes */
        -: 5177:XS_EUPXS(XS_Stats__LikeR_lm)
        -: 5178:{
        -: 5179:    dVAR; dXSARGS;
        -: 5180:    PERL_UNUSED_VAR(cv); /* -W */
        -: 5181:    PERL_UNUSED_VAR(items); /* -W */
        -: 5182:    {
        -: 5183:	SV *	RETVAL;
        -: 5184:#line 4648 "LikeR.xs"
        -: 5185:{
        -: 5186:	const char *restrict formula  = NULL;
        -: 5187:	SV *restrict data_sv = NULL;
        -: 5188:	char f_cpy[512];
        -: 5189:	char *restrict src, *restrict dst, *restrict tilde, *restrict lhs, *restrict rhs, *restrict chunk;
        -: 5190:
        -: 5191:	char **restrict terms = NULL, **restrict uniq_terms = NULL, **restrict exp_terms = NULL;
        -: 5192:	bool *restrict is_dummy = NULL;
        -: 5193:	char **restrict dummy_base = NULL, **restrict dummy_level = NULL;
        -: 5194:	unsigned int term_cap = 64, exp_cap = 64, num_terms = 0, num_uniq = 0, p = 0, p_exp = 0;
        -: 5195:	size_t n = 0, valid_n = 0, i, j, k, l, l1, l2;
        -: 5196:	bool has_intercept = true;
        -: 5197:
        -: 5198:	char **restrict row_names = NULL, **restrict valid_row_names = NULL;
        -: 5199:	HV **restrict row_hashes = NULL;
        -: 5200:	HV *restrict data_hoa = NULL;
        -: 5201:	SV *restrict ref = NULL;
        -: 5202:
        -: 5203:	double *restrict X = NULL, *restrict Y = NULL, *restrict XtX = NULL, *restrict XtY = NULL;
        -: 5204:	bool *restrict aliased = NULL;
        -: 5205:	double *restrict beta = NULL;
        -: 5206:	int final_rank = 0, df_res = 0;
        -: 5207:	HV *restrict res_hv, *restrict coef_hv, *restrict fitted_hv, *restrict resid_hv, *restrict summary_hv;
        -: 5208:	AV *restrict terms_av;
        -: 5209:	double rss = 0.0, rse_sq = 0.0;
        -: 5210:	HE *restrict entry;
        -: 5211:
        -: 5212:	if (items % 2 != 0) croak("Usage: lm(formula => 'mpg ~ wt * hp', data => \\%%mtcars)");
        -: 5213:
        -: 5214:	for (unsigned short i_arg = 0; i_arg < items; i_arg += 2) {
        -: 5215:	  const char *restrict key = SvPV_nolen(ST(i_arg));
        -: 5216:	  SV *restrict val = ST(i_arg + 1);
        -: 5217:	  if      (strEQ(key, "formula")) formula = SvPV_nolen(val);
        -: 5218:	  else if (strEQ(key, "data"))    data_sv = val;
        -: 5219:	  else croak("lm: unknown argument '%s'", key);
        -: 5220:	}
        -: 5221:	if (!formula) croak("lm: formula is required");
        -: 5222:	if (!data_sv || !SvROK(data_sv)) croak("lm: data is required and must be a reference");
        -: 5223:
        -: 5224:	// ========================================================================
        -: 5225:	// PHASE 1: Data Extraction
        -: 5226:	// ========================================================================
        -: 5227:	ref = SvRV(data_sv);
        -: 5228:	if (SvTYPE(ref) == SVt_PVHV) {
        -: 5229:	  HV *restrict hv = (HV*)ref;
        -: 5230:	  if (hv_iterinit(hv) == 0) croak("lm: Data hash is empty");
        -: 5231:	  entry = hv_iternext(hv);
        -: 5232:	  if (entry) {
        -: 5233:		   SV *restrict val = hv_iterval(hv, entry);
        -: 5234:		   if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
        -: 5235:		       data_hoa = hv;
        -: 5236:		       n = av_len((AV*)SvRV(val)) + 1;
        -: 5237:		       Newx(row_names, n, char*);
        -: 5238:		       for (i = 0; i < n; i++) {
        -: 5239:		           char buf[32];
        -: 5240:		           snprintf(buf, sizeof(buf), "%lu", (unsigned long)(i + 1));
        -: 5241:		           row_names[i] = savepv(buf);
        -: 5242:		       }
        -: 5243:		   } else if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVHV) {
        -: 5244:		       n = hv_iterinit(hv);
        -: 5245:		       Newx(row_names, n, char*); Newx(row_hashes, n, HV*);
        -: 5246:		       i = 0;
        -: 5247:		       while ((entry = hv_iternext(hv))) {
        -: 5248:		           unsigned int len;
        -: 5249:		           row_names[i] = savepv(hv_iterkey(entry, &len));
        -: 5250:		           row_hashes[i] = (HV*)SvRV(hv_iterval(hv, entry));
        -: 5251:		           i++;
        -: 5252:		       }
        -: 5253:		   } else croak("lm: Hash values must be ArrayRefs (HoA) or HashRefs (HoH)");
        -: 5254:	  }
        -: 5255:	} else if (SvTYPE(ref) == SVt_PVAV) {
        -: 5256:	  AV *restrict av = (AV*)ref; n = av_len(av) + 1;
        -: 5257:	  Newx(row_names, n, char*);
        -: 5258:	  Newx(row_hashes, n, HV*);
        -: 5259:	  for (i = 0; i < n; i++) {
        -: 5260:		   SV **restrict val = av_fetch(av, i, 0);
        -: 5261:		   if (val && SvROK(*val) && SvTYPE(SvRV(*val)) == SVt_PVHV) {
        -: 5262:		       row_hashes[i] = (HV*)SvRV(*val);
        -: 5263:		       char buf[32]; snprintf(buf, sizeof(buf), "%lu", (unsigned long)(i + 1));
        -: 5264:		       row_names[i] = savepv(buf);
        -: 5265:		   } else {
        -: 5266:		       for (k = 0; k < i; k++) Safefree(row_names[k]);
        -: 5267:		       Safefree(row_names); Safefree(row_hashes);
        -: 5268:		       croak("lm: Array values must be HashRefs (AoH)");
        -: 5269:		   }
        -: 5270:	  }
        -: 5271:	} else croak("lm: Data must be an Array or Hash reference");
        -: 5272:
        -: 5273:	// ========================================================================
        -: 5274:	// PHASE 2: Formula Parsing & `.` Expansion
        -: 5275:	// ========================================================================
        -: 5276:	src = (char*)formula; dst = f_cpy;
        -: 5277:	while (*src && (dst - f_cpy < 511)) { if (!isspace(*src)) { *dst++ = *src; } src++; }
        -: 5278:	*dst = '\0';
        -: 5279:
        -: 5280:	tilde = strchr(f_cpy, '~');
        -: 5281:	if (!tilde) {
        -: 5282:	  for (i = 0; i < n; i++) Safefree(row_names[i]);
        -: 5283:	  Safefree(row_names); if (row_hashes) Safefree(row_hashes);
        -: 5284:	  croak("lm: invalid formula, missing '~'");
        -: 5285:	}
        -: 5286:	*tilde = '\0';
        -: 5287:	lhs = f_cpy;
        -: 5288:	rhs = tilde + 1;
        -: 5289:
        -: 5290:	// Remove intercept-suppression markers from RHS.
        -: 5291:	// IMPORTANT: skip tokens that appear inside I(...) wrappers so that
        -: 5292:	// expressions like I(x^-1) are never mistakenly treated as "-1".
        -: 5293:	{
        -: 5294:	  char *restrict p_idx = rhs;
        -: 5295:	  while (*p_idx) {
        -: 5296:		   // Skip over I(...) sub-expressions entirely
        -: 5297:		   if (p_idx[0] == 'I' && p_idx[1] == '(') {
        -: 5298:		       int depth = 0;
        -: 5299:		       while (*p_idx) { if (*p_idx == '(') depth++; else if (*p_idx == ')') { depth--; if (depth == 0) { p_idx++; break; } } p_idx++; }
        -: 5300:		       continue;
        -: 5301:		   }
        -: 5302:		   // Match bare -1
        -: 5303:		   if (p_idx[0] == '-' && p_idx[1] == '1' &&
        -: 5304:		       (p_idx[2] == '\0' || p_idx[2] == '+' || p_idx[2] == '-')) {
        -: 5305:		       has_intercept = false;
        -: 5306:		       memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1);
        -: 5307:		       continue; // re-examine same position
        -: 5308:		   }
        -: 5309:		   // Match +0
        -: 5310:		   if (p_idx[0] == '+' && p_idx[1] == '0' &&
        -: 5311:		       (p_idx[2] == '\0' || p_idx[2] == '+' || p_idx[2] == '-')) {
        -: 5312:		       has_intercept = false;
        -: 5313:		       memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1);
        -: 5314:		       continue;
        -: 5315:		   }
        -: 5316:		   // Match leading 0+
        -: 5317:		   if (p_idx == rhs && p_idx[0] == '0' && p_idx[1] == '+') {
        -: 5318:		       has_intercept = false;
        -: 5319:		       memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1);
        -: 5320:		       continue;
        -: 5321:		   }
        -: 5322:		   // Match bare 0 (entire rhs)
        -: 5323:		   if (p_idx == rhs && p_idx[0] == '0' && p_idx[1] == '\0') {
        -: 5324:		       has_intercept = false; p_idx[0] = '\0'; break;
        -: 5325:		   }
        -: 5326:		   // Strip redundant +1 (keep intercept, just remove marker)
        -: 5327:		   if (p_idx[0] == '+' && p_idx[1] == '1' &&
        -: 5328:		       (p_idx[2] == '\0' || p_idx[2] == '+' || p_idx[2] == '-')) {
        -: 5329:		       memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1);
        -: 5330:		       continue;
        -: 5331:		   }
        -: 5332:		   // Strip leading bare 1 or 1+
        -: 5333:		   if (p_idx == rhs) {
        -: 5334:		       if (p_idx[0] == '1' && p_idx[1] == '\0') { p_idx[0] = '\0'; break; }
        -: 5335:		       if (p_idx[0] == '1' && p_idx[1] == '+') { memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1); continue; }
        -: 5336:		   }
        -: 5337:		   p_idx++;
        -: 5338:	  }
        -: 5339:	}
        -: 5340:
        -: 5341:	// Clean up stray `++`, leading `+`, trailing `+`
        -: 5342:	{
        -: 5343:	  char *restrict p_idx;
        -: 5344:	  while ((p_idx = strstr(rhs, "++")) != NULL)
        -: 5345:		   memmove(p_idx, p_idx + 1, strlen(p_idx + 1) + 1);
        -: 5346:	  if (rhs[0] == '+') memmove(rhs, rhs + 1, strlen(rhs + 1) + 1);
        -: 5347:	  size_t len_rhs = strlen(rhs);
        -: 5348:	  if (len_rhs > 0 && rhs[len_rhs - 1] == '+') rhs[len_rhs - 1] = '\0';
        -: 5349:	}
        -: 5350:
        -: 5351:	// Expand `.` Operator
        -: 5352:	char rhs_expanded[2048] = "";
        -: 5353:	size_t rhs_len = 0;
        -: 5354:	chunk = strtok(rhs, "+");
        -: 5355:	while (chunk != NULL) {
        -: 5356:	  if (strcmp(chunk, ".") == 0) {
        -: 5357:		   AV *cols = get_all_columns(data_hoa, row_hashes, n);
        -: 5358:		   for (size_t c = 0; c <= (size_t)av_len(cols); c++) {
        -: 5359:		       SV **col_sv = av_fetch(cols, c, 0);
        -: 5360:		       if (col_sv && SvOK(*col_sv)) {
        -: 5361:		           const char *col_name = SvPV_nolen(*col_sv);
        -: 5362:		           if (strcmp(col_name, lhs) != 0) {
        -: 5363:		               size_t slen = strlen(col_name);
        -: 5364:		               if (rhs_len + slen + 2 < sizeof(rhs_expanded)) {
        -: 5365:		                   if (rhs_len > 0) { strcat(rhs_expanded, "+"); rhs_len++; }
        -: 5366:		                   strcat(rhs_expanded, col_name);
        -: 5367:		                   rhs_len += slen;
        -: 5368:		               }
        -: 5369:		           }
        -: 5370:		       }
        -: 5371:		   }
        -: 5372:		   SvREFCNT_dec(cols);
        -: 5373:	  } else {
        -: 5374:		   size_t slen = strlen(chunk);
        -: 5375:		   if (rhs_len + slen + 2 < sizeof(rhs_expanded)) {
        -: 5376:		       if (rhs_len > 0) { strcat(rhs_expanded, "+"); rhs_len++; }
        -: 5377:		       strcat(rhs_expanded, chunk);
        -: 5378:		       rhs_len += slen;
        -: 5379:		   }
        -: 5380:	  }
        -: 5381:	  chunk = strtok(NULL, "+");
        -: 5382:	}
        -: 5383:
        -: 5384:	Newx(terms, term_cap, char*); Newx(uniq_terms, term_cap, char*);
        -: 5385:	Newx(exp_terms, exp_cap, char*); Newx(is_dummy, exp_cap, bool);
        -: 5386:	Newx(dummy_base, exp_cap, char*); Newx(dummy_level, exp_cap, char*);
        -: 5387:
        -: 5388:	if (has_intercept) { terms[num_terms++] = savepv("Intercept"); }
        -: 5389:
        -: 5390:	if (strlen(rhs_expanded) > 0) {
        -: 5391:	  chunk = strtok(rhs_expanded, "+");
        -: 5392:	  while (chunk != NULL) {
        -: 5393:		   if (num_terms >= term_cap - 3) {
        -: 5394:		       term_cap *= 2;
        -: 5395:		       Renew(terms, term_cap, char*); Renew(uniq_terms, term_cap, char*);
        -: 5396:		   }
        -: 5397:		   char *restrict star = strchr(chunk, '*');
        -: 5398:		   if (star) {
        -: 5399:		       *star = '\0';
        -: 5400:		       char *restrict left = chunk;
        -: 5401:		       char *restrict right = star + 1;
        -: 5402:		       char *restrict c_l = strchr(left, '^');
        -: 5403:		       if (c_l && strncmp(left, "I(", 2) != 0) *c_l = '\0';
        -: 5404:		       char *restrict c_r = strchr(right, '^');
        -: 5405:		       if (c_r && strncmp(right, "I(", 2) != 0) *c_r = '\0';
        -: 5406:		       terms[num_terms++] = savepv(left);
        -: 5407:		       terms[num_terms++] = savepv(right);
        -: 5408:		       size_t inter_len = strlen(left) + strlen(right) + 2;
        -: 5409:		       terms[num_terms] = (char*)safemalloc(inter_len);
        -: 5410:		       snprintf(terms[num_terms++], inter_len, "%s:%s", left, right);
        -: 5411:		   } else {
        -: 5412:		       char *restrict c_chunk = strchr(chunk, '^');
        -: 5413:		       if (c_chunk && strncmp(chunk, "I(", 2) != 0) *c_chunk = '\0';
        -: 5414:		       terms[num_terms++] = savepv(chunk);
        -: 5415:		   }
        -: 5416:		   chunk = strtok(NULL, "+");
        -: 5417:	  }
        -: 5418:	}
        -: 5419:
        -: 5420:	for (i = 0; i < num_terms; i++) {
        -: 5421:	  bool found = false;
        -: 5422:	  for (j = 0; j < num_uniq; j++) { if (strcmp(terms[i], uniq_terms[j]) == 0) { found = true; break; } }
        -: 5423:	  if (!found) uniq_terms[num_uniq++] = savepv(terms[i]);
        -: 5424:	}
        -: 5425:	p = num_uniq;
        -: 5426:
        -: 5427:	// ========================================================================
        -: 5428:	// PHASE 3: Categorical Expansion
        -: 5429:	// ========================================================================
        -: 5430:	for (j = 0; j < p; j++) {
        -: 5431:	  if (p_exp + 32 >= exp_cap) {
        -: 5432:		   exp_cap *= 2;
        -: 5433:		   Renew(exp_terms, exp_cap, char*); Renew(is_dummy, exp_cap, bool);
        -: 5434:		   Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 5435:	  }
        -: 5436:	  if (strcmp(uniq_terms[j], "Intercept") == 0) {
        -: 5437:		   exp_terms[p_exp] = savepv("Intercept"); is_dummy[p_exp] = false; p_exp++; continue;
        -: 5438:	  }
        -: 5439:	  if (is_column_categorical(data_hoa, row_hashes, n, uniq_terms[j])) {
        -: 5440:		   char **restrict levels = NULL;
        -: 5441:		   unsigned int num_levels = 0, levels_cap = 8;
        -: 5442:		   Newx(levels, levels_cap, char*);
        -: 5443:		   for (i = 0; i < n; i++) {
        -: 5444:		       char *str_val = get_data_string_alloc(data_hoa, row_hashes, i, uniq_terms[j]);
        -: 5445:		       if (str_val) {
        -: 5446:		           bool found = false;
        -: 5447:		           for (l = 0; l < num_levels; l++) { if (strcmp(levels[l], str_val) == 0) { found = true; break; } }
        -: 5448:		           if (!found) {
        -: 5449:		               if (num_levels >= levels_cap) { levels_cap *= 2; Renew(levels, levels_cap, char*); }
        -: 5450:		               levels[num_levels++] = savepv(str_val);
        -: 5451:		           }
        -: 5452:		           Safefree(str_val);
        -: 5453:		       }
        -: 5454:		   }
        -: 5455:		   if (num_levels > 0) {
        -: 5456:		       for (l1 = 0; l1 < num_levels - 1; l1++)
        -: 5457:		           for (l2 = l1 + 1; l2 < num_levels; l2++)
        -: 5458:		               if (strcmp(levels[l1], levels[l2]) > 0) { char *tmp = levels[l1]; levels[l1] = levels[l2]; levels[l2] = tmp; }
        -: 5459:		       for (l = 1; l < num_levels; l++) {
        -: 5460:		           if (p_exp >= exp_cap) {
        -: 5461:		               exp_cap *= 2;
        -: 5462:		               Renew(exp_terms, exp_cap, char*); Renew(is_dummy, exp_cap, bool);
        -: 5463:		               Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 5464:		           }
        -: 5465:		           size_t t_len = strlen(uniq_terms[j]) + strlen(levels[l]) + 1;
        -: 5466:		           exp_terms[p_exp] = (char*)safemalloc(t_len);
        -: 5467:		           snprintf(exp_terms[p_exp], t_len, "%s%s", uniq_terms[j], levels[l]);
        -: 5468:		           is_dummy[p_exp] = true;
        -: 5469:		           dummy_base[p_exp]  = savepv(uniq_terms[j]);
        -: 5470:		           dummy_level[p_exp] = savepv(levels[l]);
        -: 5471:		           p_exp++;
        -: 5472:		       }
        -: 5473:		       for (l = 0; l < num_levels; l++) Safefree(levels[l]);
        -: 5474:		       Safefree(levels);
        -: 5475:		   } else {
        -: 5476:		       Safefree(levels);
        -: 5477:		       exp_terms[p_exp] = savepv(uniq_terms[j]); is_dummy[p_exp] = false; p_exp++;
        -: 5478:		   }
        -: 5479:	  } else {
        -: 5480:		   exp_terms[p_exp] = savepv(uniq_terms[j]); is_dummy[p_exp] = false; p_exp++;
        -: 5481:	  }
        -: 5482:	}
        -: 5483:	p = p_exp;
        -: 5484:	Newx(X, n * p, double); Newx(Y, n, double);
        -: 5485:	Newx(valid_row_names, n, char*);
        -: 5486:
        -: 5487:	// ========================================================================
        -: 5488:	// PHASE 4: Matrix Construction & Listwise Deletion
        -: 5489:	// ========================================================================
        -: 5490:	for (i = 0; i < n; i++) {
        -: 5491:	  double y_val = evaluate_term(data_hoa, row_hashes, i, lhs);
        -: 5492:	  if (isnan(y_val)) { Safefree(row_names[i]); continue; }
        -: 5493:
        -: 5494:	  bool row_ok = true;
        -: 5495:	  double *restrict row_x = (double*)safemalloc(p * sizeof(double));
        -: 5496:	  for (j = 0; j < p; j++) {
        -: 5497:		   if (strcmp(exp_terms[j], "Intercept") == 0) {
        -: 5498:		       row_x[j] = 1.0;
        -: 5499:		   } else if (is_dummy[j]) {
        -: 5500:		       char *restrict str_val = get_data_string_alloc(data_hoa, row_hashes, i, dummy_base[j]);
        -: 5501:		       if (str_val) {
        -: 5502:		           row_x[j] = (strcmp(str_val, dummy_level[j]) == 0) ? 1.0 : 0.0;
        -: 5503:		           Safefree(str_val);
        -: 5504:		       } else { row_ok = false; break; }
        -: 5505:		   } else {
        -: 5506:		       row_x[j] = evaluate_term(data_hoa, row_hashes, i, exp_terms[j]);
        -: 5507:		       if (isnan(row_x[j])) { row_ok = false; break; }
        -: 5508:		   }
        -: 5509:	  }
        -: 5510:	  if (!row_ok) { Safefree(row_names[i]); Safefree(row_x); continue; }
        -: 5511:
        -: 5512:	  Y[valid_n] = y_val;
        -: 5513:	  for (j = 0; j < p; j++) X[valid_n * p + j] = row_x[j];
        -: 5514:	  valid_row_names[valid_n] = row_names[i];
        -: 5515:	  valid_n++;
        -: 5516:	  Safefree(row_x);
        -: 5517:	}
        -: 5518:	Safefree(row_names);
        -: 5519:
        -: 5520:	if (valid_n <= p) {
        -: 5521:	  for (i = 0; i < num_terms; i++) Safefree(terms[i]); Safefree(terms);
        -: 5522:	  for (i = 0; i < num_uniq; i++) Safefree(uniq_terms[i]); Safefree(uniq_terms);
        -: 5523:	  for (j = 0; j < p_exp; j++) {
        -: 5524:		   Safefree(exp_terms[j]);
        -: 5525:		   if (is_dummy[j]) { Safefree(dummy_base[j]); Safefree(dummy_level[j]); }
        -: 5526:	  }
        -: 5527:	  Safefree(exp_terms); Safefree(is_dummy); Safefree(dummy_base); Safefree(dummy_level);
        -: 5528:	  Safefree(X); Safefree(Y); Safefree(valid_row_names);
        -: 5529:	  if (row_hashes) Safefree(row_hashes);
        -: 5530:	  croak("lm: 0 degrees of freedom (too many NAs or parameters > observations)");
        -: 5531:	}
        -: 5532:
        -: 5533:	// ========================================================================
        -: 5534:	// PHASE 5: OLS Math
        -: 5535:	// ========================================================================
        -: 5536:	Newxz(XtX, p * p, double);
        -: 5537:	for (i = 0; i < p; i++)
        -: 5538:	  for (j = 0; j < p; j++) {
        -: 5539:		   double sum = 0.0;
        -: 5540:		   for (k = 0; k < valid_n; k++) sum += X[k * p + i] * X[k * p + j];
        -: 5541:		   XtX[i * p + j] = sum;
        -: 5542:	  }
        -: 5543:	Newxz(XtY, p, double);
        -: 5544:	for (i = 0; i < p; i++) {
        -: 5545:	  double sum = 0.0;
        -: 5546:	  for (k = 0; k < valid_n; k++) sum += X[k * p + i] * Y[k];
        -: 5547:	  XtY[i] = sum;
        -: 5548:	}
        -: 5549:	Newx(aliased, p, bool);
        -: 5550:	final_rank = sweep_matrix_ols(XtX, p, aliased);
        -: 5551:	Newxz(beta, p, double);
        -: 5552:	for (i = 0; i < p; i++) {
        -: 5553:	  if (aliased[i]) { beta[i] = NAN; }
        -: 5554:	  else {
        -: 5555:		   double sum = 0.0;
        -: 5556:		   for (j = 0; j < p; j++) if (!aliased[j]) sum += XtX[i * p + j] * XtY[j];
        -: 5557:		   beta[i] = sum;
        -: 5558:	  }
       48: 5559:	}
call    0 returned 48
       48: 5560:
        -: 5561:	// ========================================================================
       48: 5562:	// PHASE 6: Metrics & Cleanup
        -: 5563:	// ========================================================================
        -: 5564:	res_hv = newHV(); coef_hv = newHV(); fitted_hv = newHV(); resid_hv = newHV();
        -: 5565:	summary_hv = newHV(); terms_av = newAV();
        -: 5566:
function XS_Stats__LikeR_seq called 18 returned 100% blocks executed 67%
       18: 5567:	df_res = (int)valid_n - final_rank;
        -: 5568:
       18: 5569:	// rss / mss accumulated here — rse_sq computed AFTER this loop (not before)
       18: 5569-block  0
call    0 returned 18
       18: 5570:	double sum_y = 0.0, mss = 0.0;
branch  0 taken 18 (fallthrough)
branch  1 taken 0
       18: 5570-block  0
branch  2 taken 0 (fallthrough)
branch  3 taken 18
    #####: 5571:	for (i = 0; i < valid_n; i++) sum_y += Y[i];
    %%%%%: 5571-block  0
call    0 never executed
        -: 5572:	double mean_y = sum_y / (double)valid_n;
       18: 5573:
        -: 5574:	for (i = 0; i < valid_n; i++) {
       18: 5575:	  double y_hat = 0.0;
       18: 5575-block  0
call    0 returned 18
        -: 5576:	  for (j = 0; j < p; j++) if (!aliased[j]) y_hat += X[i * p + j] * beta[j];
       18: 5577:	  double res   = Y[i] - y_hat;
call    0 returned 18
        -: 5578:	  rss          += res * res;
        -: 5579:	  double diff_m = has_intercept ? (y_hat - mean_y) : y_hat;
        -: 5580:	  mss          += diff_m * diff_m;
       18: 5581:	  hv_store(fitted_hv, valid_row_names[i], strlen(valid_row_names[i]), newSVnv(y_hat), 0);
branch  0 taken 3 (fallthrough)
branch  1 taken 15
        3: 5582:	  hv_store(resid_hv,  valid_row_names[i], strlen(valid_row_names[i]), newSVnv(res),   0);
        3: 5582-block  0
        -: 5583:	  Safefree(valid_row_names[i]);
       15: 5584:	}
       15: 5584-block  0
call    0 returned 15
        -: 5585:	Safefree(valid_row_names);
        -: 5586:
        -: 5587:	// Single, authoritative rse_sq calculation
        -: 5588:	rse_sq = (df_res > 0) ? (rss / (double)df_res) : NAN;
        -: 5589:
        -: 5590:	int df_int = has_intercept ? 1 : 0;
        -: 5591:	double r_squared = 0.0, adj_r_squared = 0.0, f_stat = NAN, f_pvalue = NAN;
        -: 5592:	int numdf = final_rank - df_int;
        -: 5593:
        -: 5594:	if (final_rank != df_int && (mss + rss) > 0.0) {
        -: 5595:	  r_squared     = mss / (mss + rss);
        -: 5596:	  adj_r_squared = 1.0 - (1.0 - r_squared) * ((valid_n - df_int) / (double)df_res);
        -: 5597:	  if (rse_sq > 0.0 && numdf > 0) {
        -: 5598:		   f_stat   = (mss / (double)numdf) / rse_sq;
        -: 5599:		   f_pvalue = 1.0 - pf(f_stat, (double)numdf, (double)df_res);
        -: 5600:	  } else if (rse_sq == 0.0) {
        -: 5601:		   f_stat   = INFINITY;
        -: 5602:		   f_pvalue = 0.0;
        -: 5603:	  }
        -: 5604:	} else if (final_rank == df_int) {
        -: 5605:	  r_squared = 0.0; adj_r_squared = 0.0;
        -: 5606:	}
        -: 5607:
        -: 5608:	for (j = 0; j < p; j++) {
        -: 5609:	  hv_store(coef_hv, exp_terms[j], strlen(exp_terms[j]), newSVnv(beta[j]), 0);
        -: 5610:	  av_push(terms_av, newSVpv(exp_terms[j], 0));
        -: 5611:	  HV *restrict row_hv = newHV();
        -: 5612:	  if (aliased[j]) {
        -: 5613:		   hv_store(row_hv, "Estimate",   8,  newSVpv("NaN", 0), 0);
        -: 5614:		   hv_store(row_hv, "Std. Error", 10, newSVpv("NaN", 0), 0);
        -: 5615:		   hv_store(row_hv, "t value",    7,  newSVpv("NaN", 0), 0);
        -: 5616:		   hv_store(row_hv, "Pr(>|t|)",   8,  newSVpv("NaN", 0), 0);
        -: 5617:	  } else {
        -: 5618:		   double se    = sqrt(rse_sq * XtX[j * p + j]);
        -: 5619:		   double t_val = (se > 0.0) ? (beta[j] / se) : (INFINITY * (beta[j] >= 0.0 ? 1.0 : -1.0));
        -: 5620:		   double p_val = get_t_pvalue(t_val, df_res, "two.sided");
        -: 5621:		   hv_store(row_hv, "Estimate",   8,  newSVnv(beta[j]), 0);
        -: 5622:		   hv_store(row_hv, "Std. Error", 10, newSVnv(se),      0);
        -: 5623:		   hv_store(row_hv, "t value",    7,  newSVnv(t_val),   0);
        -: 5624:		   hv_store(row_hv, "Pr(>|t|)",   8,  newSVnv(p_val),   0);
function XS_Stats__LikeR_rnorm called 6 returned 50% blocks executed 87%
        6: 5625:	  }
        -: 5626:	  hv_store(summary_hv, exp_terms[j], strlen(exp_terms[j]), newRV_noinc((SV*)row_hv), 0);
        6: 5627:	}
        6: 5627-block  0
call    0 returned 6
branch  1 taken 3 (fallthrough)
branch  2 taken 3
        -: 5628:
        -: 5629:	hv_store(res_hv, "coefficients",  12, newRV_noinc((SV*)coef_hv),   0);
        -: 5630:	hv_store(res_hv, "fitted.values", 13, newRV_noinc((SV*)fitted_hv), 0);
        -: 5631:	hv_store(res_hv, "residuals",      9, newRV_noinc((SV*)resid_hv),  0);
        -: 5632:	hv_store(res_hv, "df.residual",   11, newSVuv(df_res),             0);
        -: 5633:	hv_store(res_hv, "rank",           4, newSVuv(final_rank),         0);
        -: 5634:	hv_store(res_hv, "rss",            3, newSVnv(rss),                0);
        -: 5635:	hv_store(res_hv, "summary",        7, newRV_noinc((SV*)summary_hv),0);
        -: 5636:	hv_store(res_hv, "terms",          5, newRV_noinc((SV*)terms_av),  0);
        -: 5637:	hv_store(res_hv, "r.squared",      9, newSVnv(r_squared),          0);
        -: 5638:	hv_store(res_hv, "adj.r.squared", 13, newSVnv(adj_r_squared),      0);
        -: 5639:	if (!isnan(f_stat)) {
        -: 5640:	  AV *fstat_av = newAV();
        -: 5641:	  av_push(fstat_av, newSVnv(f_stat));
        -: 5642:	  av_push(fstat_av, newSViv(numdf));
        -: 5643:	  av_push(fstat_av, newSViv(df_res));
        -: 5644:	  hv_store(res_hv, "fstatistic", 10, newRV_noinc((SV*)fstat_av), 0);
        -: 5645:	  hv_store(res_hv, "f.pvalue",    8, newSVnv(f_pvalue),          0);
        -: 5646:	}
        -: 5647:
        -: 5648:	// Deep Cleanup
        -: 5649:	for (i = 0; i < num_terms; i++) Safefree(terms[i]); Safefree(terms);
        -: 5650:	for (i = 0; i < num_uniq; i++) Safefree(uniq_terms[i]); Safefree(uniq_terms);
        -: 5651:	for (j = 0; j < p_exp; j++) {
        -: 5652:	  Safefree(exp_terms[j]);
        -: 5653:	  if (is_dummy[j]) { Safefree(dummy_base[j]); Safefree(dummy_level[j]); }
        -: 5654:	}
        -: 5655:	Safefree(exp_terms); Safefree(is_dummy); Safefree(dummy_base); Safefree(dummy_level);
        -: 5656:	Safefree(X); Safefree(Y); Safefree(XtX); Safefree(XtY);
        -: 5657:	Safefree(beta); Safefree(aliased);
        -: 5658:	if (row_hashes) Safefree(row_hashes);
        -: 5659:
        -: 5660:	RETVAL = newRV_noinc((SV*)res_hv);
        -: 5661:}
        -: 5662:#line 5663 "LikeR.c"
        -: 5663:	RETVAL = sv_2mortal(RETVAL);
        -: 5664:	ST(0) = RETVAL;
        -: 5665:    }
        -: 5666:    XSRETURN(1);
        -: 5667:}
        -: 5668:
        -: 5669:
        -: 5670:XS_EUPXS(XS_Stats__LikeR_seq); /* prototype to pass -Wmissing-prototypes */
        -: 5671:XS_EUPXS(XS_Stats__LikeR_seq)
        -: 5672:{
        -: 5673:    dVAR; dXSARGS;
        -: 5674:    if (items < 2 || items > 3)
        -: 5675:       croak_xs_usage(cv,  "from, to, by = 1.0");
        -: 5676:    PERL_UNUSED_VAR(ax); /* -Wall */
        -: 5677:    SP -= items;
        -: 5678:    {
        -: 5679:	double	from = (double)SvNV(ST(0))
        -: 5680:;
        -: 5681:	double	to = (double)SvNV(ST(1))
        -: 5682:;
        -: 5683:	double	by;
        -: 5684:
        -: 5685:	if (items < 3)
        -: 5686:	    by = 1.0;
        -: 5687:	else {
        3: 5688:	    by = (double)SvNV(ST(2))
call    0 returned 3
        3: 5689:;
        -: 5690:	}
        3: 5691:#line 5133 "LikeR.xs"
        -: 5692:	{
        -: 5693:		//Handle the zero 'by' case
        -: 5694:		if (by == 0.0) {
        -: 5695:			if (from == to) {
function XS_Stats__LikeR_aov called 27 returned 67% blocks executed 78%
       27: 5696:				 EXTEND(SP, 1);
        -: 5697:				 mPUSHn(from);
       27: 5698:				 XSRETURN(1);
       27: 5698-block  0
call    0 returned 27
       27: 5699:			} else {
branch  0 taken 0 (fallthrough)
branch  1 taken 27
    #####: 5700:				 croak("invalid 'by' argument: cannot be zero when from != to");
    %%%%%: 5700-block  0
call    0 never executed
        -: 5701:			}
       27: 5702:		}
        -: 5703:		// Check for wrong direction / infinite loop
       27: 5704:		if ((from < to && by < 0.0) || (from > to && by > 0.0)) {
       27: 5704-block  0
call    0 returned 27
        -: 5705:			croak("wrong sign in 'by' argument");
        -: 5706:		}
        -: 5707:		/* * Calculate number of elements. 
        -: 5708:		* R uses a small epsilon (like 1e-10) to avoid dropping the last 
        -: 5709:		* element due to floating point inaccuracies.
        -: 5710:		*/
        -: 5711:		double n_elements_d = (to - from) / by;
        -: 5712:		if (n_elements_d < 0.0) n_elements_d = 0.0;
        -: 5713:		size_t n_elements = (n_elements_d + 1e-10) + 1;
        -: 5714:		// Pre-extend the stack to avoid reallocating inside the loop
        -: 5715:		EXTEND(SP, n_elements);
        -: 5716:		for (size_t i = 0; i < n_elements; i++) {
        -: 5717:			mPUSHn(from + i * by);
        -: 5718:		}
        -: 5719:		XSRETURN(n_elements);
        -: 5720:	}
        -: 5721:#line 5722 "LikeR.c"
        -: 5722:	PUTBACK;
        -: 5723:	return;
        -: 5724:    }
        -: 5725:}
        -: 5726:
        -: 5727:
        -: 5728:XS_EUPXS(XS_Stats__LikeR_rnorm); /* prototype to pass -Wmissing-prototypes */
        -: 5729:XS_EUPXS(XS_Stats__LikeR_rnorm)
        -: 5730:{
        -: 5731:    dVAR; dXSARGS;
        -: 5732:    PERL_UNUSED_VAR(cv); /* -W */
        -: 5733:    PERL_UNUSED_VAR(items); /* -W */
        -: 5734:    {
        -: 5735:	SV *	RETVAL;
        -: 5736:#line 5165 "LikeR.xs"
        -: 5737:    {
        -: 5738:        // Auto-seed the PRNG if the Perl script hasn't done so yet
        -: 5739:        AUTO_SEED_PRNG();
        -: 5740:
        -: 5741:        size_t n = 0;
        -: 5742:        double mean = 0.0, sd = 1.0;
        -: 5743:        int arg_start = 0;
        -: 5744:
        -: 5745:        // Check if the first argument is a simple integer (rnorm(33))
        -: 5746:        if (items > 0 && SvIOK(ST(0)) && (items == 1 || items % 2 != 0)) {
        -: 5747:            n = (unsigned int)SvUV(ST(0));
        -: 5748:            arg_start = 1; // Start parsing named arguments from the second element
        -: 5749:        }
        -: 5750:
        -: 5751:        // --- Parse remaining named arguments from the flat stack ---
        -: 5752:        if ((items - arg_start) % 2 != 0) {
        -: 5753:            croak("Usage: rnorm(n), rnorm(n => 10, mean => 0, sd => 1), or rnorm(33, mean => 0)");
        -: 5754:        }
        -: 5755:
        -: 5756:        for (int i = arg_start; i < items; i += 2) {
        -: 5757:            const char* restrict key = SvPV_nolen(ST(i));
        -: 5758:            SV* restrict val = ST(i + 1);
        -: 5759:
        -: 5760:            if      (strEQ(key, "n"))    n    = (unsigned int)SvUV(val);
        -: 5761:            else if (strEQ(key, "mean")) mean = SvNV(val);
        -: 5762:            else if (strEQ(key, "sd"))   sd   = SvNV(val);
        -: 5763:            else croak("rnorm: unknown argument '%s'", key);
        -: 5764:        }
        -: 5765:
        -: 5766:        if (sd < 0.0) croak("rnorm: standard deviation must be non-negative");
        -: 5767:
        -: 5768:        AV *restrict result_av = newAV();
        -: 5769:        if (n > 0) {
        -: 5770:            av_extend(result_av, n - 1);
        -: 5771:            // Generate random normals using the Box-Muller transform
        -: 5772:            for (size_t i = 0; i < n; ) {
        -: 5773:                 double u, v, s;
        -: 5774:                 do {
        -: 5775:                     // Drand01() hooks into Perl's internal PRNG, respecting Perl's srand()
        -: 5776:                     u = 2.0 * Drand01() - 1.0;
        -: 5777:                     v = 2.0 * Drand01() - 1.0;
        -: 5778:                     s = u * u + v * v;
        -: 5779:                 } while (s >= 1.0 || s == 0.0);
        -: 5780:
        -: 5781:                 double mul = sqrt(-2.0 * log(s) / s);
        -: 5782:                 // Box-Muller generates two independent values per iteration
        -: 5783:                 av_store(result_av, i++, newSVnv(mean + sd * u * mul));
        -: 5784:                 if (i < n) {
        -: 5785:                     av_store(result_av, i++, newSVnv(mean + sd * v * mul));
        -: 5786:                 }
        -: 5787:            }
        -: 5788:        }
        -: 5789:        RETVAL = newRV_noinc((SV*)result_av);
        -: 5790:    }
        -: 5791:#line 5792 "LikeR.c"
        -: 5792:	RETVAL = sv_2mortal(RETVAL);
        -: 5793:	ST(0) = RETVAL;
        -: 5794:    }
        -: 5795:    XSRETURN(1);
        -: 5796:}
        -: 5797:
        -: 5798:
        -: 5799:XS_EUPXS(XS_Stats__LikeR_aov); /* prototype to pass -Wmissing-prototypes */
        -: 5800:XS_EUPXS(XS_Stats__LikeR_aov)
        -: 5801:{
        -: 5802:    dVAR; dXSARGS;
        -: 5803:    if (items != 2)
        -: 5804:       croak_xs_usage(cv,  "data_sv, formula_sv");
        -: 5805:    {
        -: 5806:	SV*	data_sv = ST(0)
        -: 5807:;
        -: 5808:	SV*	formula_sv = ST(1)
        -: 5809:;
        -: 5810:	SV *	RETVAL;
        -: 5811:#line 5226 "LikeR.xs"
        -: 5812:	{
        -: 5813:	  const char *restrict formula = SvPV_nolen(formula_sv);
        -: 5814:	  char f_cpy[512];
        -: 5815:	  char *restrict src, *restrict dst, *restrict tilde, *restrict lhs, *restrict rhs, *restrict chunk;
        -: 5816:
        -: 5817:	  char **restrict terms = NULL, **restrict uniq_terms = NULL, **restrict exp_terms = NULL, **restrict parent_term = NULL;
        -: 5818:	  bool *restrict is_dummy = NULL, *is_interact = NULL;
        -: 5819:	  char **restrict dummy_base = NULL, **restrict dummy_level = NULL;
        -: 5820:	  int *restrict term_map = NULL, *restrict left_idx = NULL, *restrict right_idx = NULL;
        -: 5821:	  unsigned int term_cap = 64, exp_cap = 64, num_terms = 0, num_uniq = 0, p = 0, p_exp = 0;
        -: 5822:	  size_t n = 0, valid_n = 0, i, j;
        -: 5823:	  bool has_intercept = true;
        -: 5824:
        -: 5825:	  char **restrict row_names = NULL;
        -: 5826:	  HV **restrict row_hashes = NULL;
        -: 5827:	  HV *restrict data_hoa = NULL;
        -: 5828:	  SV *restrict ref = NULL;
        -: 5829:	  HE *restrict entry;
        -: 5830:	  double **restrict X_mat = NULL;
        -: 5831:	  double *restrict Y = NULL;
        -: 5832:
        -: 5833:	  char **restrict term_base_level = NULL;  /* reference level for each uniq_term (NULL if not categorical) */
        -: 5834:	  if (!SvROK(data_sv)) croak("aov: data is required and must be a reference");
        -: 5835:	  // ========================================================================
        -: 5836:	  // PHASE 1: Data Extraction
        -: 5837:	  // ========================================================================
        -: 5838:	  ref = SvRV(data_sv);
        -: 5839:	  if (SvTYPE(ref) == SVt_PVHV) {
        -: 5840:		   HV*restrict hv = (HV*)ref;
        -: 5841:		   if (hv_iterinit(hv) == 0) croak("aov: Data hash is empty");
        -: 5842:		   entry = hv_iternext(hv);
        -: 5843:		   if (entry) {
        -: 5844:		        SV*restrict val = hv_iterval(hv, entry);
        -: 5845:		        if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {
        -: 5846:		            data_hoa = hv;
        -: 5847:		            n = av_len((AV*)SvRV(val)) + 1;
        -: 5848:		            Newx(row_names, n, char*);
        -: 5849:		            for(i = 0; i < n; i++) { 
        -: 5850:		                char buf[32]; snprintf(buf, sizeof(buf), "%lu", (unsigned long)(i+1));
        -: 5851:		                row_names[i] = savepv(buf); 
        -: 5852:		            }
        -: 5853:		        } else if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVHV) {
        -: 5854:		            n = hv_iterinit(hv);
        -: 5855:		            Newx(row_names, n, char*); Newx(row_hashes, n, HV*);
        -: 5856:		            i = 0;
        -: 5857:		            while ((entry = hv_iternext(hv))) {
        -: 5858:		                unsigned int len;
        -: 5859:		                row_names[i] = savepv(hv_iterkey(entry, &len));
        -: 5860:		                row_hashes[i] = (HV*)SvRV(hv_iterval(hv, entry));
        -: 5861:		                i++;
        -: 5862:		            }
        -: 5863:		        } else croak("aov: Hash values must be ArrayRefs (HoA) or HashRefs (HoH)");
        -: 5864:		   }
        -: 5865:	  } else if (SvTYPE(ref) == SVt_PVAV) {
        -: 5866:		   AV*restrict av = (AV*)ref;
        -: 5867:		   n = av_len(av) + 1;
        -: 5868:		   Newx(row_names, n, char*);
        -: 5869:		   Newx(row_hashes, n, HV*);
        -: 5870:		   for (i = 0; i < n; i++) {
        -: 5871:		        SV**restrict val = av_fetch(av, i, 0);
        -: 5872:		        if (val && SvROK(*val) && SvTYPE(SvRV(*val)) == SVt_PVHV) {
        -: 5873:		            row_hashes[i] = (HV*)SvRV(*val);
        -: 5874:		            char buf[32];
        -: 5875:		            snprintf(buf, sizeof(buf), "%lu", (unsigned long)(i + 1));
        -: 5876:		            row_names[i] = savepv(buf);
        -: 5877:		        } else {
        -: 5878:		            for (size_t k = 0; k < i; k++) Safefree(row_names[k]);
        -: 5879:		            Safefree(row_names); Safefree(row_hashes);
        -: 5880:		            croak("aov: Array values must be HashRefs (AoH)");
        -: 5881:		        }
        -: 5882:		   }
        -: 5883:	  } else croak("aov: Data must be an Array or Hash reference");
        -: 5884:
        -: 5885:	  // ========================================================================
        -: 5886:	  // PHASE 2: Formula Parsing & `.` Expansion
        -: 5887:	  // ========================================================================
        -: 5888:	  src = (char*)formula; dst = f_cpy;
        -: 5889:	  while (*src && (dst - f_cpy < 511)) { if (!isspace(*src)) { *dst++ = *src; } src++; }
        -: 5890:	  *dst = '\0';
        -: 5891:
        -: 5892:	  tilde = strchr(f_cpy, '~');
        -: 5893:	  if (!tilde) {
        -: 5894:		   for (i = 0; i < n; i++) Safefree(row_names[i]);
        -: 5895:		   Safefree(row_names); if (row_hashes) Safefree(row_hashes);
        -: 5896:		   croak("aov: invalid formula, missing '~'");
        -: 5897:	  }
        -: 5898:	  *tilde = '\0';
        -: 5899:	  lhs = f_cpy;
        -: 5900:	  rhs = tilde + 1;
        -: 5901:
        -: 5902:	  char *restrict p_idx;
        -: 5903:	  while ((p_idx = strstr(rhs, "-1")) != NULL) { has_intercept = false; memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1); }
        -: 5904:	  while ((p_idx = strstr(rhs, "+0")) != NULL) { has_intercept = false; memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1); }
        -: 5905:	  while ((p_idx = strstr(rhs, "0+")) != NULL) { has_intercept = false; memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1); }
        -: 5906:	  if (rhs[0] == '0' && rhs[1] == '\0')        { has_intercept = false; rhs[0] = '\0'; }
        -: 5907:	  while ((p_idx = strstr(rhs, "+1")) != NULL) { memmove(p_idx, p_idx + 2, strlen(p_idx + 2) + 1); }
        -: 5908:	  if (rhs[0] == '1' && rhs[1] == '\0')        { rhs[0] = '\0'; } 
        -: 5909:	  else if (rhs[0] == '1' && rhs[1] == '+')    { memmove(rhs, rhs + 2, strlen(rhs + 2) + 1); }
        -: 5910:
        -: 5911:	  while ((p_idx = strstr(rhs, "++")) != NULL) memmove(p_idx, p_idx + 1, strlen(p_idx + 1) + 1);
        -: 5912:	  if (rhs[0] == '+') memmove(rhs, rhs + 1, strlen(rhs + 1) + 1);
        -: 5913:	  size_t len_rhs = strlen(rhs);
        -: 5914:	  if (len_rhs > 0 && rhs[len_rhs - 1] == '+') rhs[len_rhs - 1] = '\0';
        -: 5915:
        -: 5916:	  char rhs_expanded[2048] = "";
        -: 5917:	  size_t rhs_len = 0;
        -: 5918:	  chunk = strtok(rhs, "+");
        -: 5919:	  while (chunk != NULL) {
        -: 5920:		   if (strcmp(chunk, ".") == 0) {
        -: 5921:		        AV *restrict cols = get_all_columns(data_hoa, row_hashes, n);
        -: 5922:		        for (size_t c = 0; c <= av_len(cols); c++) {
        -: 5923:		            SV **restrict col_sv = av_fetch(cols, c, 0);
        -: 5924:		            if (col_sv && SvOK(*col_sv)) {
        -: 5925:		                const char *restrict col_name = SvPV_nolen(*col_sv);
        -: 5926:		                if (strcmp(col_name, lhs) != 0) {
        -: 5927:		                    size_t slen = strlen(col_name);
        -: 5928:		                    if (rhs_len + slen + 2 < sizeof(rhs_expanded)) {
        -: 5929:		                        if (rhs_len > 0) { strcat(rhs_expanded, "+"); rhs_len++; }
        -: 5930:		                        strcat(rhs_expanded, col_name);
        -: 5931:		                        rhs_len += slen;
        -: 5932:		                    }
        -: 5933:		                }
        -: 5934:		            }
        -: 5935:		        }
        -: 5936:		        SvREFCNT_dec(cols);
        -: 5937:		   } else {
        -: 5938:		        size_t slen = strlen(chunk);
        -: 5939:		        if (rhs_len + slen + 2 < sizeof(rhs_expanded)) {
        -: 5940:		            if (rhs_len > 0) { strcat(rhs_expanded, "+"); rhs_len++; }
        -: 5941:		            strcat(rhs_expanded, chunk);
        -: 5942:		            rhs_len += slen;
        -: 5943:		        }
        -: 5944:		   }
        -: 5945:		   chunk = strtok(NULL, "+");
        -: 5946:	  }
        -: 5947:
        -: 5948:	  // Setup arrays safely
        -: 5949:	  Newx(terms, term_cap, char*);
        -: 5950:	  Newx(uniq_terms, term_cap, char*);
        -: 5951:	  Newx(exp_terms, exp_cap, char*); Newx(parent_term, exp_cap, char*);
        -: 5952:	  Newx(is_dummy, exp_cap, bool); Newx(is_interact, exp_cap, bool);
        -: 5953:	  Newx(dummy_base, exp_cap, char*); Newx(dummy_level, exp_cap, char*);
        -: 5954:	  Newx(term_map, exp_cap, int); Newx(left_idx, exp_cap, int); Newx(right_idx, exp_cap, int);
        -: 5955:
        -: 5956:	  if (has_intercept) { terms[num_terms++] = savepv("Intercept"); }
        -: 5957:
        -: 5958:	  if (strlen(rhs_expanded) > 0) {
        -: 5959:		   chunk = strtok(rhs_expanded, "+");
        -: 5960:		   while (chunk != NULL) {
        -: 5961:		        if (num_terms >= term_cap - 3) {
        -: 5962:		            term_cap *= 2;
        -: 5963:		            Renew(terms, term_cap, char*); Renew(uniq_terms, term_cap, char*);
        -: 5964:		        }
        -: 5965:		        char *restrict star = strchr(chunk, '*');
        -: 5966:		        if (star) {
        -: 5967:		            *star = '\0';
        -: 5968:		            char *restrict left = chunk;
        -: 5969:		            char *right = star + 1;
        -: 5970:		            char *restrict c_l = strchr(left, '^');
        -: 5971:		            if (c_l && strncmp(left, "I(", 2) != 0) *c_l = '\0';
        -: 5972:		            char *restrict c_r = strchr(right, '^'); if (c_r && strncmp(right, "I(", 2) != 0) *c_r = '\0';
        -: 5973:		            terms[num_terms++] = savepv(left);
        -: 5974:		            terms[num_terms++] = savepv(right);
        -: 5975:		            size_t inter_len = strlen(left) + strlen(right) + 2;
        -: 5976:		            terms[num_terms] = (char*)safemalloc(inter_len);
        -: 5977:		            snprintf(terms[num_terms++], inter_len, "%s:%s", left, right);
        -: 5978:		        } else {
        -: 5979:		            char *restrict c_chunk = strchr(chunk, '^'); 
        -: 5980:		            if (c_chunk && strncmp(chunk, "I(", 2) != 0) *c_chunk = '\0';
        -: 5981:		            terms[num_terms++] = savepv(chunk);
        -: 5982:		        }
        -: 5983:		        chunk = strtok(NULL, "+");
        -: 5984:		   }
        -: 5985:	  }
        -: 5986:
        -: 5987:	  for (i = 0; i < num_terms; i++) {
        -: 5988:		   bool found = false;
        -: 5989:		   for (size_t k = 0; k < num_uniq; k++) {
        -: 5990:		         if (strcmp(terms[i], uniq_terms[k]) == 0) { found = true; break; }
        -: 5991:		   }
        -: 5992:		   if (!found) uniq_terms[num_uniq++] = savepv(terms[i]);
        -: 5993:	  }
        -: 5994:	  p = num_uniq;
        -: 5995:
        -: 5996:	  /* ---- NEW: allocate one slot per unique term, zero-initialised so
        -: 5997:		           non-categorical terms stay NULL                          ---- */
        -: 5998:	  Newxz(term_base_level, num_uniq, char*);
        -: 5999:	  /* ----------------------------------------------------------------------- */
        -: 6000:
        -: 6001:	  // ========================================================================
        -: 6002:	  // PHASE 3: Categorical & Interaction Expansion
        -: 6003:	  // ========================================================================
        -: 6004:	  for (j = 0; j < p; j++) {
        -: 6005:		   if (p_exp + 64 >= exp_cap) {
        -: 6006:		         exp_cap *= 2;
        -: 6007:		         Renew(exp_terms, exp_cap, char*); Renew(parent_term, exp_cap, char*);
        -: 6008:		         Renew(is_dummy, exp_cap, bool); Renew(is_interact, exp_cap, bool);
        -: 6009:		         Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 6010:		         Renew(term_map, exp_cap, int); Renew(left_idx, exp_cap, int); Renew(right_idx, exp_cap, int);
        -: 6011:		   }
        -: 6012:
        -: 6013:		   if (strcmp(uniq_terms[j], "Intercept") == 0) {
        -: 6014:		         exp_terms[p_exp] = savepv("Intercept");
        -: 6015:		         parent_term[p_exp] = savepv("Intercept");
        -: 6016:		         is_dummy[p_exp] = false; is_interact[p_exp] = false;
        -: 6017:		         term_map[p_exp] = j;
        -: 6018:		         p_exp++;
        -: 6019:		         continue;
        -: 6020:		   }
        -: 6021:
        -: 6022:		   char *restrict colon = strchr(uniq_terms[j], ':');
        -: 6023:		   if (colon) {
        -: 6024:		         char left[256], right[256];
        -: 6025:		         strncpy(left, uniq_terms[j], colon - uniq_terms[j]);
        -: 6026:		         left[colon - uniq_terms[j]] = '\0';
        -: 6027:		         strcpy(right, colon + 1);
        -: 6028:
        -: 6029:		         int *restrict l_indices = (int*)safemalloc(p_exp * sizeof(int)); int l_count = 0;
        -: 6030:		         int *restrict r_indices = (int*)safemalloc(p_exp * sizeof(int)); int r_count = 0;
        -: 6031:		         for (size_t e = 0; e < p_exp; e++) {
        -: 6032:		             if (strcmp(parent_term[e], left) == 0) l_indices[l_count++] = e;
        -: 6033:		             if (strcmp(parent_term[e], right) == 0) r_indices[r_count++] = e;
        -: 6034:		         }
        -: 6035:
        -: 6036:		         if (l_count == 0 || r_count == 0) {
        -: 6037:		             Safefree(l_indices); Safefree(r_indices);
        -: 6038:		             croak("aov: Interaction term '%s' requires its main effects to be explicitly included in the formula", uniq_terms[j]);
        -: 6039:		         } else {
        -: 6040:		             for (int li = 0; li < l_count; li++) {
        -: 6041:		                 for (int ri = 0; ri < r_count; ri++) {
        -: 6042:		                     if (p_exp >= exp_cap) {
        -: 6043:		                         exp_cap *= 2;
        -: 6044:		                         Renew(exp_terms, exp_cap, char*); Renew(parent_term, exp_cap, char*);
        -: 6045:		                         Renew(is_dummy, exp_cap, bool); Renew(is_interact, exp_cap, bool);
        -: 6046:		                         Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 6047:		                         Renew(term_map, exp_cap, int); Renew(left_idx, exp_cap, int); Renew(right_idx, exp_cap, int);
        -: 6048:		                     }
        -: 6049:		                     size_t t_len = strlen(exp_terms[l_indices[li]]) + strlen(exp_terms[r_indices[ri]]) + 2;
        -: 6050:		                     exp_terms[p_exp] = (char*)safemalloc(t_len);
        -: 6051:		                     snprintf(exp_terms[p_exp], t_len, "%s:%s", exp_terms[l_indices[li]], exp_terms[r_indices[ri]]);
        -: 6052:		                     parent_term[p_exp] = savepv(uniq_terms[j]);
        -: 6053:		                     is_dummy[p_exp] = false; is_interact[p_exp] = true;
        -: 6054:		                     left_idx[p_exp] = l_indices[li];
        -: 6055:		                     right_idx[p_exp] = r_indices[ri];
        -: 6056:		                     term_map[p_exp] = j;
        -: 6057:		                     p_exp++;
        -: 6058:		                 }
        -: 6059:		             }
        -: 6060:		         }
        -: 6061:		         Safefree(l_indices); Safefree(r_indices);
        -: 6062:		   } else {
        -: 6063:		         if (is_column_categorical(data_hoa, row_hashes, n, uniq_terms[j])) {
        -: 6064:		             char **restrict levels = NULL;
        -: 6065:		             unsigned int num_levels = 0, levels_cap = 8;
        -: 6066:		             Newx(levels, levels_cap, char*);
        -: 6067:		             for (i = 0; i < n; i++) {
        -: 6068:		                 char* str_val = get_data_string_alloc(data_hoa, row_hashes, i, uniq_terms[j]);
        -: 6069:		                 if (str_val) {
        -: 6070:		                     bool found = false;
        -: 6071:		                     for (size_t l = 0; l < num_levels; l++) {
        -: 6072:		                         if (strcmp(levels[l], str_val) == 0) { found = true; break; }
        -: 6073:		                     }
        -: 6074:		                     if (!found) {
        -: 6075:		                         if (num_levels >= levels_cap) { levels_cap *= 2; Renew(levels, levels_cap, char*); }
        -: 6076:		                         levels[num_levels++] = savepv(str_val);
        -: 6077:		                     }
        -: 6078:		                     Safefree(str_val);
        -: 6079:		                 }
        -: 6080:		             }
        -: 6081:
        -: 6082:		             if (num_levels > 0) {
        -: 6083:		                 for (size_t l1 = 0; l1 < num_levels - 1; l1++) {
        -: 6084:		                     for (size_t l2 = l1 + 1; l2 < num_levels; l2++) {
        -: 6085:		                         if (strcmp(levels[l1], levels[l2]) > 0) {
        -: 6086:		                             char *tmp = levels[l1]; levels[l1] = levels[l2]; levels[l2] = tmp;
        -: 6087:		                         }
        -: 6088:		                     }
        -: 6089:		                 }
        -: 6090:
        -: 6091:		                 /* ---- NEW: record the reference (base) level for this term ---- */
        -: 6092:		                 term_base_level[j] = savepv(levels[0]);
        -: 6093:		                 /* --------------------------------------------------------------- */
        -: 6094:
        -: 6095:		                 for (size_t l = 1; l < num_levels; l++) {
        -: 6096:		                     if (p_exp >= exp_cap) {
        -: 6097:		                         exp_cap *= 2;
        -: 6098:		                         Renew(exp_terms, exp_cap, char*); Renew(parent_term, exp_cap, char*);
        -: 6099:		                         Renew(is_dummy, exp_cap, bool); Renew(is_interact, exp_cap, bool);
        -: 6100:		                         Renew(dummy_base, exp_cap, char*); Renew(dummy_level, exp_cap, char*);
        -: 6101:		                         Renew(term_map, exp_cap, int); Renew(left_idx, exp_cap, int); Renew(right_idx, exp_cap, int);
        -: 6102:		                     }
        -: 6103:		                     size_t t_len = strlen(uniq_terms[j]) + strlen(levels[l]) + 1;
        -: 6104:		                     exp_terms[p_exp] = (char*)safemalloc(t_len);
        -: 6105:		                     snprintf(exp_terms[p_exp], t_len, "%s%s", uniq_terms[j], levels[l]);
        -: 6106:		                     parent_term[p_exp] = savepv(uniq_terms[j]);
        -: 6107:		                     is_dummy[p_exp] = true; is_interact[p_exp] = false;
        -: 6108:		                     dummy_base[p_exp] = savepv(uniq_terms[j]);
        -: 6109:		                     dummy_level[p_exp] = savepv(levels[l]);
        -: 6110:		                     term_map[p_exp] = j;
        -: 6111:		                     p_exp++;
        -: 6112:		                 }
        -: 6113:		                 for (size_t l = 0; l < num_levels; l++) Safefree(levels[l]);
        -: 6114:		                 Safefree(levels);
        -: 6115:		             } else {
        -: 6116:		                 Safefree(levels);
        -: 6117:		                 exp_terms[p_exp] = savepv(uniq_terms[j]);
        -: 6118:		                 parent_term[p_exp] = savepv(uniq_terms[j]);
        -: 6119:		                 is_dummy[p_exp] = false; is_interact[p_exp] = false;
        -: 6120:		                 term_map[p_exp] = j;
        -: 6121:		                 p_exp++;
        -: 6122:		             }
        -: 6123:		         } else {
        -: 6124:		             exp_terms[p_exp] = savepv(uniq_terms[j]);
        -: 6125:		             parent_term[p_exp] = savepv(uniq_terms[j]);
        -: 6126:		             is_dummy[p_exp] = false; is_interact[p_exp] = false;
        -: 6127:		             term_map[p_exp] = j;
        -: 6128:		             p_exp++;
        -: 6129:		         }
        -: 6130:		   }
        -: 6131:	  }
        -: 6132:	  X_mat = (double**)safemalloc(n * sizeof(double*));
        -: 6133:	  for(i = 0; i < n; i++) X_mat[i] = (double*)safemalloc(p_exp * sizeof(double));
        -: 6134:	  Newx(Y, n, double);
        -: 6135:	  // ========================================================================
        -: 6136:	  // PHASE 4: Matrix Construction & Listwise Deletion
        -: 6137:	  // ========================================================================
        -: 6138:	  for (i = 0; i < n; i++) {
        -: 6139:		   double y_val = evaluate_term(data_hoa, row_hashes, i, lhs);
        -: 6140:		   if (isnan(y_val)) { Safefree(row_names[i]); continue; }
        -: 6141:		   bool row_ok = true;
        -: 6142:		   double *restrict row_x = (double*)safemalloc(p_exp * sizeof(double));
        -: 6143:		   for (j = 0; j < p_exp; j++) {
        -: 6144:		         if (strcmp(exp_terms[j], "Intercept") == 0) {
        -: 6145:		             row_x[j] = 1.0;
        -: 6146:		         } else if (is_interact[j]) {
        -: 6147:		             row_x[j] = row_x[left_idx[j]] * row_x[right_idx[j]];
       18: 6148:		         } else if (is_dummy[j]) {
call    0 returned 18
       18: 6149:		             char*restrict str_val = get_data_string_alloc(data_hoa, row_hashes, i, dummy_base[j]);
        -: 6150:		             if (str_val) {
       18: 6151:		                 row_x[j] = (strcmp(str_val, dummy_level[j]) == 0) ? 1.0 : 0.0;
        -: 6152:		                 Safefree(str_val);
        -: 6153:		             } else { row_ok = false; break; }
        -: 6154:		         } else {
        -: 6155:		             row_x[j] = evaluate_term(data_hoa, row_hashes, i, parent_term[j]);
function XS_Stats__LikeR_fisher_test called 18 returned 83% blocks executed 89%
       18: 6156:		             if (isnan(row_x[j])) { row_ok = false; break; }
        -: 6157:		         }
       18: 6158:		   }
       18: 6158-block  0
call    0 returned 18
branch  1 taken 3 (fallthrough)
branch  2 taken 15
        -: 6159:		   if (!row_ok) { Safefree(row_names[i]); Safefree(row_x); continue; }
        -: 6160:
        -: 6161:		   Y[valid_n] = y_val;
        -: 6162:		   for (j = 0; j < p_exp; j++) X_mat[valid_n][j] = row_x[j];
        -: 6163:		   valid_n++;
        -: 6164:		   Safefree(row_x);
        -: 6165:		   Safefree(row_names[i]);
        -: 6166:	  }
        -: 6167:	  Safefree(row_names);
        -: 6168:	  if (valid_n <= p_exp) {
        -: 6169:		   // Full Clean Up 
        -: 6170:		   for (i = 0; i < num_terms; i++) Safefree(terms[i]); Safefree(terms);
        -: 6171:		   for (i = 0; i < num_uniq; i++) Safefree(uniq_terms[i]); Safefree(uniq_terms);
        -: 6172:		   for (j = 0; j < p_exp; j++) {
        -: 6173:		        Safefree(exp_terms[j]); Safefree(parent_term[j]);
        -: 6174:		        if (is_dummy[j]) { Safefree(dummy_base[j]); Safefree(dummy_level[j]); }
        -: 6175:		   }
        -: 6176:		   Safefree(exp_terms); Safefree(parent_term); 
        -: 6177:		   Safefree(is_dummy); Safefree(is_interact); 
        -: 6178:		   Safefree(dummy_base); Safefree(dummy_level);
        -: 6179:		   Safefree(term_map); Safefree(left_idx); Safefree(right_idx);
        -: 6180:		   for(i = 0; i < n; i++) Safefree(X_mat[i]);
        -: 6181:		   Safefree(X_mat); Safefree(Y);
        -: 6182:		   if (row_hashes) Safefree(row_hashes);
        -: 6183:		   /* ---- NEW ---- */
        -: 6184:		   for (i = 0; i < num_uniq; i++) { if (term_base_level[i]) Safefree(term_base_level[i]); }
        -: 6185:		   Safefree(term_base_level);
        -: 6186:		   /* ------------ */
        -: 6187:		   croak("aov: 0 degrees of freedom (too many NAs or parameters > observations)");
        -: 6188:	  }
        -: 6189:	// ========================================================================
        -: 6190:	  // PHASE 5: Math & Output Formatting
        -: 6191:	  // ========================================================================
        -: 6192:	  bool *restrict aliased_qr = (bool*)safemalloc(p_exp * sizeof(bool));
        -: 6193:	  size_t *restrict rank_map = (size_t*)safemalloc(p_exp * sizeof(size_t));
        -: 6194:	  apply_householder_aov(X_mat, Y, valid_n, p_exp, aliased_qr, rank_map);
        -: 6195:	  double *restrict term_ss;
        -: 6196:	  int *restrict term_df;
        -: 6197:	  Newxz(term_ss, num_uniq, double);
        -: 6198:	  Newxz(term_df, num_uniq, int);
        -: 6199:
        -: 6200:	  for (i = 0; i < p_exp; i++) {
        -: 6201:		   if (strcmp(exp_terms[i], "Intercept") == 0) continue; 
        -: 6202:		   if (aliased_qr[i]) continue;
        -: 6203:		   int t_idx = term_map[i];
        -: 6204:		   size_t r_k = rank_map[i];
        -: 6205:		   term_ss[t_idx] += Y[r_k] * Y[r_k];
        -: 6206:		   term_df[t_idx] += 1;
        -: 6207:	  }
        -: 6208:	  int rank = 0;
        -: 6209:	  for (i = 0; i < p_exp; i++) {
        -: 6210:		   if (!aliased_qr[i]) rank++;
        -: 6211:	  }
        -: 6212:	  double rss_prev = 0.0;
        -: 6213:	  for (i = rank; i < valid_n; i++) {
        -: 6214:		   rss_prev += Y[i] * Y[i];
        -: 6215:	  }
        -: 6216:	  int res_df = valid_n - rank;
        -: 6217:	  double ms_res = (res_df > 0) ? rss_prev / res_df : 0.0;
        -: 6218:
        -: 6219:	  HV*restrict ret_hash = newHV();
        -: 6220:	  for (j = 0; j < num_uniq; j++) {
        -: 6221:		   if (strcmp(uniq_terms[j], "Intercept") == 0) continue;
        -: 6222:		   HV*restrict term_stats = newHV();
        -: 6223:		   double ss = term_ss[j];
        -: 6224:		   int df = term_df[j];
        -: 6225:		   double ms = (df > 0) ? ss / df : 0.0;
        -: 6226:
        -: 6227:		   hv_stores(term_stats, "Df", newSViv(df));
        -: 6228:		   hv_stores(term_stats, "Sum Sq", newSVnv(ss));
        -: 6229:		   hv_stores(term_stats, "Mean Sq", newSVnv(ms));
        -: 6230:		   if (ms_res > 0.0 && df > 0) {
        -: 6231:		         double f_val = ms / ms_res;
        -: 6232:		         hv_stores(term_stats, "F value", newSVnv(f_val));
        -: 6233:		         hv_stores(term_stats, "Pr(>F)", newSVnv(1.0 - pf(f_val, (double)df, (double)res_df)));
        -: 6234:		   } else {
        -: 6235:		         hv_stores(term_stats, "F value", newSVnv(NAN));
        -: 6236:		         hv_stores(term_stats, "Pr(>F)", newSVnv(NAN));
        -: 6237:		   }
        -: 6238:		   hv_store(ret_hash, uniq_terms[j], strlen(uniq_terms[j]), newRV_noinc((SV*)term_stats), 0);
        -: 6239:	  }
        -: 6240:
        -: 6241:	  HV*restrict res_stats = newHV();
        -: 6242:	  hv_stores(res_stats, "Df", newSViv(res_df));
        -: 6243:	  hv_stores(res_stats, "Sum Sq", newSVnv(rss_prev));
        -: 6244:	  hv_stores(res_stats, "Mean Sq", newSVnv(ms_res));
        -: 6245:	  hv_stores(ret_hash, "Residuals", newRV_noinc((SV*)res_stats));
        -: 6246:
        -: 6247:	  /* ---- NEW: group_stats => { mean => {...}, size => {...} } ---- */
        -: 6248:	  {
        -: 6249:		   AV *restrict all_cols = get_all_columns(data_hoa, row_hashes, n);
        -: 6250:		   HV *restrict mean_hv  = newHV();
        -: 6251:		   HV *restrict size_hv  = newHV();
        -: 6252:		   for (size_t c = 0; c <= (size_t)av_len(all_cols); c++) {
        -: 6253:		       SV **restrict col_sv = av_fetch(all_cols, c, 0);
        -: 6254:		       if (!col_sv || !SvOK(*col_sv)) continue;
        -: 6255:		       const char *restrict col_name = SvPV_nolen(*col_sv);
        -: 6256:		       double col_sum = 0.0;
        -: 6257:		       IV     col_count = 0;
        -: 6258:		       for (i = 0; i < n; i++) {
        -: 6259:		           double val = evaluate_term(data_hoa, row_hashes, i, col_name);
        -: 6260:		           if (!isnan(val)) { col_sum += val; col_count++; }
        -: 6261:		       }
        -: 6262:		       double col_mean = (col_count > 0) ? col_sum / col_count : NAN;
        -: 6263:		       hv_store(mean_hv, col_name, strlen(col_name), newSVnv(col_mean), 0);
        -: 6264:		       hv_store(size_hv, col_name, strlen(col_name), newSViv(col_count), 0);
        -: 6265:		   }
        -: 6266:		   SvREFCNT_dec(all_cols);
       15: 6267:		   HV *restrict gs_hv = newHV();
call    0 returned 15
       15: 6268:		   hv_stores(gs_hv, "mean", newRV_noinc((SV*)mean_hv));
        -: 6269:		   hv_stores(gs_hv, "size", newRV_noinc((SV*)size_hv));
       15: 6270:		   hv_stores(ret_hash, "group_stats", newRV_noinc((SV*)gs_hv));
        -: 6271:	  }
        -: 6272:	  /* -------------------------------------------------------------- */
        -: 6273:
        -: 6274:	  // Deep Cleanup
function XS_Stats__LikeR_power_t_test called 21 returned 100% blocks executed 71%
       21: 6275:	  for (i = 0; i < num_terms; i++) Safefree(terms[i]); Safefree(terms);
        -: 6276:	  for (i = 0; i < num_uniq; i++) Safefree(uniq_terms[i]); Safefree(uniq_terms);
       21: 6277:	  for (j = 0; j < p_exp; j++) {
       21: 6277-block  0
call    0 returned 21
branch  1 taken 0 (fallthrough)
branch  2 taken 21
        -: 6278:		   Safefree(exp_terms[j]); Safefree(parent_term[j]);
        -: 6279:		   if (is_dummy[j]) { Safefree(dummy_base[j]); Safefree(dummy_level[j]); }
        -: 6280:	  }
        -: 6281:	  Safefree(exp_terms); Safefree(parent_term); 
        -: 6282:	  Safefree(is_dummy); Safefree(is_interact); 
        -: 6283:	  Safefree(dummy_base); Safefree(dummy_level);
        -: 6284:	  Safefree(term_map); Safefree(left_idx); Safefree(right_idx);
        -: 6285:	  Safefree(term_ss); Safefree(term_df);
        -: 6286:	  for (i = 0; i < n; i++) Safefree(X_mat[i]);
        -: 6287:	  Safefree(X_mat); Safefree(Y);
        -: 6288:	  Safefree(aliased_qr); Safefree(rank_map);
        -: 6289:	  if (row_hashes) Safefree(row_hashes);
        -: 6290:	  RETVAL = newRV_noinc((SV*)ret_hash);
        -: 6291:	}
        -: 6292:#line 6293 "LikeR.c"
        -: 6293:	RETVAL = sv_2mortal(RETVAL);
        -: 6294:	ST(0) = RETVAL;
        -: 6295:    }
        -: 6296:    XSRETURN(1);
        -: 6297:}
        -: 6298:
        -: 6299:
        -: 6300:XS_EUPXS(XS_Stats__LikeR_fisher_test); /* prototype to pass -Wmissing-prototypes */
        -: 6301:XS_EUPXS(XS_Stats__LikeR_fisher_test)
        -: 6302:{
        -: 6303:    dVAR; dXSARGS;
        -: 6304:    PERL_UNUSED_VAR(cv); /* -W */
        -: 6305:    PERL_UNUSED_VAR(items); /* -W */
        -: 6306:    {
        -: 6307:	SV *	RETVAL;
        -: 6308:#line 5713 "LikeR.xs"
        -: 6309:{
        -: 6310:	if (items < 1) croak("fisher_test requires at least a data reference");
        -: 6311:
        -: 6312:	SV*restrict data_ref = ST(0);
        -: 6313:	double conf_level = 0.95;
        -: 6314:	const char*restrict alternative = "two.sided";
        -: 6315:
        -: 6316:	// Parse named arguments
        -: 6317:	for (unsigned short int i = 1; i < items; i += 2) {
        -: 6318:		if (i + 1 >= items) croak("fisher_test: odd number of arguments");
        -: 6319:		const char*restrict key = SvPV_nolen(ST(i));
        -: 6320:		SV*restrict val = ST(i + 1);
        -: 6321:		if (strEQ(key, "conf_level") || strEQ(key, "conf.level")) {
        -: 6322:			conf_level = SvNV(val);
        -: 6323:		} else if (strEQ(key, "alternative")) {
        -: 6324:			alternative = SvPV_nolen(val);
        -: 6325:		}
        -: 6326:	}
        -: 6327:
        -: 6328:	if (!SvROK(data_ref)) croak("fisher_test requires a reference to an Array or Hash");
        -: 6329:	SV*restrict deref = SvRV(data_ref);
        -: 6330:	size_t a = 0, b = 0, c = 0, d = 0;
        -: 6331:	// Extract Data
        -: 6332:	if (SvTYPE(deref) == SVt_PVAV) {
        -: 6333:	  AV*restrict outer = (AV*)deref;
        -: 6334:	  if (av_len(outer) != 1) croak("Outer array must have exactly 2 rows");
        -: 6335:	  SV**restrict row1_ptr = av_fetch(outer, 0, 0);
        -: 6336:	  SV**restrict row2_ptr = av_fetch(outer, 1, 0);
        -: 6337:	  if (row1_ptr && row2_ptr && SvROK(*row1_ptr) && SvROK(*row2_ptr)) {
        -: 6338:		   AV*restrict row1 = (AV*)SvRV(*row1_ptr);
        -: 6339:		   AV*restrict row2 = (AV*)SvRV(*row2_ptr);
        -: 6340:		   SV**restrict a_ptr = av_fetch(row1, 0, 0);
        -: 6341:		   SV**restrict b_ptr = av_fetch(row1, 1, 0);
        -: 6342:		   SV**restrict c_ptr = av_fetch(row2, 0, 0);
        -: 6343:		   SV**restrict d_ptr = av_fetch(row2, 1, 0);
        -: 6344:		   a = (a_ptr && SvOK(*a_ptr)) ? SvIV(*a_ptr) : 0;
        -: 6345:		   b = (b_ptr && SvOK(*b_ptr)) ? SvIV(*b_ptr) : 0;
        -: 6346:		   c = (c_ptr && SvOK(*c_ptr)) ? SvIV(*c_ptr) : 0;
        -: 6347:		   d = (d_ptr && SvOK(*d_ptr)) ? SvIV(*d_ptr) : 0;
        -: 6348:	  } else {
        -: 6349:		  croak("Invalid 2D Array structure");
        -: 6350:	  }
        -: 6351:	} else if (SvTYPE(deref) == SVt_PVHV) {
        -: 6352:		// Fixed 2D Hash Logic: Sort keys lexically to enforce structured rows/columns
        -: 6353:		HV*restrict outer = (HV*)deref;
        -: 6354:		if (hv_iterinit(outer) != 2) croak("Outer hash must have exactly 2 keys");
        -: 6355:		HE*restrict he1 = hv_iternext(outer);
        -: 6356:		HE*restrict he2 = hv_iternext(outer);
        -: 6357:		if (!he1 || !he2) croak("Invalid outer hash");
        -: 6358:		const char*restrict k1 = SvPV_nolen(hv_iterkeysv(he1));
        -: 6359:		const char*restrict k2 = SvPV_nolen(hv_iterkeysv(he2));
        -: 6360:		HE*restrict row1_he = (strcmp(k1, k2) < 0) ? he1 : he2;
        -: 6361:		HE*restrict row2_he = (strcmp(k1, k2) < 0) ? he2 : he1;
        -: 6362:		SV*restrict row1_sv = hv_iterval(outer, row1_he);
        -: 6363:		SV*restrict row2_sv = hv_iterval(outer, row2_he);
        -: 6364:		if (!SvROK(row1_sv) || SvTYPE(SvRV(row1_sv)) != SVt_PVHV ||
        -: 6365:			!SvROK(row2_sv) || SvTYPE(SvRV(row2_sv)) != SVt_PVHV) {
        -: 6366:			croak("Inner elements must be hashes");
        -: 6367:		}
        -: 6368:		HV*restrict in1 = (HV*)SvRV(row1_sv);
        -: 6369:		HV*restrict in2 = (HV*)SvRV(row2_sv);
        -: 6370:		if (hv_iterinit(in1) != 2 || hv_iterinit(in2) != 2) croak("Inner hashes must have exactly 2 keys");
        -: 6371:		HE*restrict in1_he1 = hv_iternext(in1);
        -: 6372:		HE*restrict in1_he2 = hv_iternext(in1);
        -: 6373:		const char*restrict in1_k1 = SvPV_nolen(hv_iterkeysv(in1_he1));
        -: 6374:		const char*restrict in1_k2 = SvPV_nolen(hv_iterkeysv(in1_he2));
        -: 6375:		HE*restrict in1_c1 = (strcmp(in1_k1, in1_k2) < 0) ? in1_he1 : in1_he2;
        -: 6376:		HE*restrict in1_c2 = (strcmp(in1_k1, in1_k2) < 0) ? in1_he2 : in1_he1;
        -: 6377:		HE*restrict in2_he1 = hv_iternext(in2);
        -: 6378:		HE*restrict in2_he2 = hv_iternext(in2);
        -: 6379:		const char*restrict in2_k1 = SvPV_nolen(hv_iterkeysv(in2_he1));
        -: 6380:		const char*restrict in2_k2 = SvPV_nolen(hv_iterkeysv(in2_he2));
        -: 6381:		HE*restrict in2_c1 = (strcmp(in2_k1, in2_k2) < 0) ? in2_he1 : in2_he2;
        -: 6382:		HE*restrict in2_c2 = (strcmp(in2_k1, in2_k2) < 0) ? in2_he2 : in2_he1;
        -: 6383:		a = (hv_iterval(in1, in1_c1) && SvOK(hv_iterval(in1, in1_c1))) ? SvIV(hv_iterval(in1, in1_c1)) : 0;
        -: 6384:		b = (hv_iterval(in1, in1_c2) && SvOK(hv_iterval(in1, in1_c2))) ? SvIV(hv_iterval(in1, in1_c2)) : 0;
        -: 6385:		c = (hv_iterval(in2, in2_c1) && SvOK(hv_iterval(in2, in2_c1))) ? SvIV(hv_iterval(in2, in2_c1)) : 0;
        -: 6386:		d = (hv_iterval(in2, in2_c2) && SvOK(hv_iterval(in2, in2_c2))) ? SvIV(hv_iterval(in2, in2_c2)) : 0;
        -: 6387:	} else {
       21: 6388:	  croak("Input must be a 2D Array or 2D Hash");
call    0 returned 21
       21: 6389:	}
        -: 6390:
       21: 6391:	// Perform Calculations via Helpers
        -: 6392:	double p_val = exact_p_value(a, b, c, d, alternative);
        -: 6393:	double mle_or, ci_low, ci_high;
        -: 6394:	calculate_exact_stats(a, b, c, d, conf_level, alternative, &mle_or, &ci_low, &ci_high);
        -: 6395:
function XS_Stats__LikeR_kruskal_test called 8 returned 100% blocks executed 88%
        8: 6396:	// Construct the Return HashRef purely in C
        -: 6397:	HV*restrict ret_hash = newHV();
        8: 6398:	hv_stores(ret_hash, "method", newSVpv("Fisher's Exact Test for Count Data", 0));
        8: 6398-block  0
call    0 returned 8
branch  1 taken 8 (fallthrough)
branch  2 taken 0
        -: 6399:	hv_stores(ret_hash, "alternative", newSVpv(alternative, 0));
        -: 6400:	AV*restrict ci_array = newAV();
        -: 6401:	av_push(ci_array, newSVnv(ci_low));
        -: 6402:	av_push(ci_array, newSVnv(ci_high));
        -: 6403:	hv_stores(ret_hash, "conf_int", newRV_noinc((SV*)ci_array));
        -: 6404:	HV*restrict est_hash = newHV();
        -: 6405:	hv_stores(ret_hash, "estimate", newRV_noinc((SV*)est_hash));
        -: 6406:	hv_stores(est_hash, "odds ratio", newSVnv(mle_or));
        -: 6407:	hv_stores(ret_hash, "p_value", newSVnv(p_val));
        -: 6408:	// Return the HashRef
        -: 6409:	RETVAL = newRV_noinc((SV*)ret_hash);
        -: 6410:}
        -: 6411:#line 6412 "LikeR.c"
        -: 6412:	RETVAL = sv_2mortal(RETVAL);
        -: 6413:	ST(0) = RETVAL;
        -: 6414:    }
        -: 6415:    XSRETURN(1);
        -: 6416:}
        -: 6417:
        -: 6418:
        -: 6419:XS_EUPXS(XS_Stats__LikeR_power_t_test); /* prototype to pass -Wmissing-prototypes */
        -: 6420:XS_EUPXS(XS_Stats__LikeR_power_t_test)
        -: 6421:{
        -: 6422:    dVAR; dXSARGS;
        -: 6423:    PERL_UNUSED_VAR(cv); /* -W */
        -: 6424:    PERL_UNUSED_VAR(items); /* -W */
        -: 6425:    {
        -: 6426:	SV *	RETVAL;
        -: 6427:#line 5820 "LikeR.xs"
        -: 6428:{
        -: 6429:    SV* sv_n = NULL;
        -: 6430:    SV* sv_delta = NULL;
        -: 6431:    SV* sv_sd = NULL;
        -: 6432:    SV* sv_sig_level = NULL;
        -: 6433:    SV* sv_power = NULL;
        -: 6434:
        -: 6435:    const char* restrict type = "two.sample";
        -: 6436:    const char* restrict alternative = "two.sided";
        -: 6437:    bool strict = false;
        -: 6438:    double tol = pow(2.2204460492503131e-16, 0.25); 
        -: 6439:
        -: 6440:    if (items % 2 != 0) croak("Usage: power_t_test(n => 30, delta => 0.5, sd => 1.0, ...)");
        -: 6441:    for (unsigned short int i = 0; i < items; i += 2) {
        -: 6442:        const char* restrict key = SvPV_nolen(ST(i));
        -: 6443:        SV* restrict val = ST(i+1);
        -: 6444:
        -: 6445:        if      (strEQ(key, "n"))           sv_n = val;
        -: 6446:        else if (strEQ(key, "delta"))       sv_delta = val;
        -: 6447:        else if (strEQ(key, "sd"))          sv_sd = val;
        -: 6448:        else if (strEQ(key, "sig.level") || strEQ(key, "sig_level")) sv_sig_level = val;
        -: 6449:        else if (strEQ(key, "power"))       sv_power = val;
        -: 6450:        else if (strEQ(key, "type"))        type = SvPV_nolen(val);
        -: 6451:        else if (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 6452:        else if (strEQ(key, "strict"))      strict = SvTRUE(val);
        -: 6453:        else if (strEQ(key, "tol"))         tol = SvNV(val);
        -: 6454:        else croak("power_t_test: unknown argument '%s'", key);
        -: 6455:    }
        -: 6456:
        -: 6457:    bool is_null_n = (!sv_n || !SvOK(sv_n));
        -: 6458:    bool is_null_delta = (!sv_delta || !SvOK(sv_delta));
        -: 6459:    bool is_null_power = (!sv_power || !SvOK(sv_power));
        -: 6460:    bool is_null_sd = (sv_sd && !SvOK(sv_sd)); 
        -: 6461:    bool is_null_sig_level = (sv_sig_level && !SvOK(sv_sig_level));
        -: 6462:
        -: 6463:    int missing_count = 0;
        -: 6464:    if (is_null_n) missing_count++;
        -: 6465:    if (is_null_delta) missing_count++;
        -: 6466:    if (is_null_power) missing_count++;
        -: 6467:    if (is_null_sd) missing_count++;
        -: 6468:    if (is_null_sig_level) missing_count++;
        -: 6469:
        -: 6470:    if (missing_count != 1) {
        -: 6471:        croak("power_t_test: exactly one of 'n', 'delta', 'sd', 'power', and 'sig_level' must be undef/NULL");
        -: 6472:    }
        -: 6473:
        -: 6474:    double n = is_null_n ? 0.0 : SvNV(sv_n);
        -: 6475:    double delta = is_null_delta ? 0.0 : SvNV(sv_delta);
        -: 6476:    double sd = (!sv_sd || is_null_sd) ? 1.0 : SvNV(sv_sd);
        -: 6477:    double sig_level = (!sv_sig_level || is_null_sig_level) ? 0.05 : SvNV(sv_sig_level);
        -: 6478:    double power = is_null_power ? 0.0 : SvNV(sv_power);
        -: 6479:    short int tsample = (strEQ(type, "one.sample") || strEQ(type, "paired")) ? 1 : 2;
        -: 6480:    short int tside = (strEQ(alternative, "one.sided") || strEQ(alternative, "greater") || strEQ(alternative, "less")) ? 1 : 2;
        -: 6481:    if (tside == 2 && !is_null_delta) delta = fabs(delta);
        -: 6482:    if (is_null_power) {
        -: 6483:        power = p_body(n, delta, sd, sig_level, tsample, tside, strict);
        -: 6484:    } else if (is_null_n) {
        -: 6485:        double low = 2.0, high = 1e7;
        -: 6486:        while (p_body(high, delta, sd, sig_level, tsample, tside, strict) < power && high < 1e12) high *= 2.0;
        -: 6487:        while (high - low > tol) {
        -: 6488:            double mid = low + (high - low) / 2.0;
        -: 6489:            if (p_body(mid, delta, sd, sig_level, tsample, tside, strict) < power) low = mid;
        -: 6490:            else high = mid;
        -: 6491:        }
        -: 6492:        n = low + (high - low) / 2.0;
        -: 6493:    } else if (is_null_sd) {
        -: 6494:        double low = delta * 1e-7, high = delta * 1e7;
        -: 6495:        while (high - low > tol) {
        -: 6496:            double mid = low + (high - low) / 2.0;
        -: 6497:            if (p_body(n, delta, mid, sig_level, tsample, tside, strict) > power) low = mid;
        -: 6498:            else high = mid;
        -: 6499:        }
        -: 6500:        sd = low + (high - low) / 2.0;
        -: 6501:    } else if (is_null_delta) {
        -: 6502:        double low = sd * 1e-7, high = sd * 1e7;
        -: 6503:        while (p_body(n, high, sd, sig_level, tsample, tside, strict) < power && high < 1e12) high *= 2.0;
        -: 6504:        while (high - low > tol) {
        -: 6505:            double mid = low + (high - low) / 2.0;
        -: 6506:            if (p_body(n, mid, sd, sig_level, tsample, tside, strict) < power) low = mid;
        -: 6507:            else high = mid;
        -: 6508:        }
        -: 6509:        delta = low + (high - low) / 2.0;
        -: 6510:    } else if (is_null_sig_level) {
        -: 6511:        double low = 1e-10, high = 1.0 - 1e-10;
        -: 6512:        while (high - low > tol) {
        -: 6513:            double mid = low + (high - low) / 2.0;
        -: 6514:            if (p_body(n, delta, sd, mid, tsample, tside, strict) < power) low = mid;
        -: 6515:            else high = mid;
        -: 6516:        }
        -: 6517:        sig_level = low + (high - low) / 2.0;
        -: 6518:    }
        -: 6519:    HV*restrict ret = newHV();
        -: 6520:    hv_stores(ret, "n", newSVnv(n));
        -: 6521:    hv_stores(ret, "delta", newSVnv(delta));
        -: 6522:    hv_stores(ret, "sd", newSVnv(sd));
        -: 6523:    hv_stores(ret, "sig.level", newSVnv(sig_level));
        -: 6524:    hv_stores(ret, "power", newSVnv(power));
        -: 6525:    hv_stores(ret, "alternative", newSVpv(alternative, 0));
        -: 6526:    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";
        -: 6527:    hv_stores(ret, "method", newSVpv(m_str, 0));
        -: 6528:    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" : "");
        -: 6529:    if (n_str[0] != '\0') hv_stores(ret, "note", newSVpv(n_str, 0));
        -: 6530:    RETVAL = newRV_noinc((SV*)ret);
        -: 6531:}
        -: 6532:#line 6533 "LikeR.c"
        -: 6533:	RETVAL = sv_2mortal(RETVAL);
        -: 6534:	ST(0) = RETVAL;
        -: 6535:    }
        -: 6536:    XSRETURN(1);
        -: 6537:}
        -: 6538:
        -: 6539:
        -: 6540:XS_EUPXS(XS_Stats__LikeR_kruskal_test); /* prototype to pass -Wmissing-prototypes */
        -: 6541:XS_EUPXS(XS_Stats__LikeR_kruskal_test)
        -: 6542:{
        -: 6543:    dVAR; dXSARGS;
        -: 6544:    PERL_UNUSED_VAR(cv); /* -W */
        -: 6545:    PERL_UNUSED_VAR(items); /* -W */
        -: 6546:    {
        -: 6547:	SV *	RETVAL;
        -: 6548:#line 5929 "LikeR.xs"
        -: 6549:{
        -: 6550:    SV *restrict x_sv = NULL, *restrict g_sv = NULL, *restrict h_sv = NULL;
        -: 6551:    unsigned int arg_idx = 0;
        -: 6552:
        -: 6553:    // 1. Shift positional arguments
        -: 6554:    //    Accept either: (arrayref, arrayref) or (hashref)
        -: 6555:    if (arg_idx < items && SvROK(ST(arg_idx))) {
        -: 6556:        svtype t = SvTYPE(SvRV(ST(arg_idx)));
        -: 6557:        if (t == SVt_PVAV) {
        -: 6558:            x_sv = ST(arg_idx++);
        -: 6559:        } else if (t == SVt_PVHV) {
        -: 6560:            h_sv = ST(arg_idx++);          /* hash-of-arrays shortcut */
        -: 6561:        }
        -: 6562:    }
        -: 6563:    if (!h_sv && arg_idx < items
        -: 6564:              && SvROK(ST(arg_idx))
        -: 6565:              && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 6566:        g_sv = ST(arg_idx++);
        -: 6567:    }
        -: 6568:    // 2. Parse named arguments (fallback)
        -: 6569:    for (; arg_idx < items; arg_idx += 2) {
        -: 6570:        const char *restrict key = SvPV_nolen(ST(arg_idx));
        -: 6571:        SV         *restrict val = ST(arg_idx + 1);
        -: 6572:        if      (strEQ(key, "x")) x_sv = val;
        -: 6573:        else if (strEQ(key, "g")) g_sv = val;
        8: 6574:        else if (strEQ(key, "h")) h_sv = val;
call    0 returned 8
        8: 6575:        else croak("kruskal_test: unknown argument '%s'", key);
        -: 6576:    }
        8: 6577:    // 3. Mutual-exclusion guard
        -: 6578:    if (h_sv && (x_sv || g_sv))
        -: 6579:        croak("kruskal_test: cannot mix 'h' (hash-of-arrays) with 'x'/'g' inputs");
        -: 6580:
        -: 6581:    /* ------------------------------------------------------------------ */
        -: 6582:    /* Shared state filled by whichever input branch runs                 */
        -: 6583:    /* ------------------------------------------------------------------ */
function boot_Stats__LikeR called 3 returned 100% blocks executed 100%
        3: 6584:    RankInfo *restrict ri = NULL;
        -: 6585:    char **restrict group_names = NULL; /* Track names to build group_stats */
        -: 6586:    size_t valid_n = 0;
        -: 6587:    size_t k       = 0;
        -: 6588:
        3: 6589:    /* ------------------------------------------------------------------ */
        3: 6589-block  0
call    0 returned 3
        -: 6590:    /* 4a. Hash-of-arrays input path                                      */
        -: 6591:    /*     my %x = ( group1 => [...], group2 => [...], ... )              */
        -: 6592:    /* ------------------------------------------------------------------ */
        -: 6593:    if (h_sv) {
        3: 6594:        if (!SvROK(h_sv) || SvTYPE(SvRV(h_sv)) != SVt_PVHV)
        -: 6595:            croak("kruskal_test: 'h' must be a HASH reference");
        -: 6596:        HV *restrict h_hv = (HV*)SvRV(h_sv);
        -: 6597:
        -: 6598:        // First pass – validate values and tally total elements
        -: 6599:        size_t total = 0;
        -: 6600:        hv_iterinit(h_hv);
        -: 6601:        HE *restrict he;
        -: 6602:        while ((he = hv_iternext(h_hv))) {
        -: 6603:            SV *restrict val = HeVAL(he);
        -: 6604:            if (!SvROK(val) || SvTYPE(SvRV(val)) != SVt_PVAV)
        -: 6605:                croak("kruskal_test: every value in 'h' must be an ARRAY reference");
        -: 6606:            total += (size_t)(av_len((AV*)SvRV(val)) + 1);
        -: 6607:        }
        3: 6608:        if (total < 2) croak("not enough observations");
call    0 returned 3
        3: 6609:
call    0 returned 3
        3: 6610:        ri = (RankInfo *)safemalloc(total * sizeof(RankInfo));
call    0 returned 3
        3: 6611:
call    0 returned 3
        3: 6612:        size_t num_keys = HvKEYS(h_hv);
call    0 returned 3
        3: 6613:        group_names = (char **)safecalloc(num_keys, sizeof(char*));
call    0 returned 3
        3: 6614:
call    0 returned 3
        3: 6615:        /* Second pass – fill ri[], assigning one group_id per hash key */
call    0 returned 3
        3: 6616:        size_t group_id = 0;
call    0 returned 3
        3: 6617:        hv_iterinit(h_hv);
call    0 returned 3
        3: 6618:        while ((he = hv_iternext(h_hv))) {
call    0 returned 3
        3: 6619:            STRLEN klen;
call    0 returned 3
        3: 6620:            const char *key_str = HePV(he, klen);
call    0 returned 3
        3: 6621:            group_names[group_id] = savepvn(key_str, klen); // Save string key
call    0 returned 3
        3: 6622:
call    0 returned 3
        3: 6623:            AV    *restrict av  = (AV*)SvRV(HeVAL(he));
call    0 returned 3
        3: 6624:            size_t          n_g = (size_t)(av_len(av) + 1);
call    0 returned 3
        3: 6625:            for (size_t i = 0; i < n_g; i++) {
call    0 returned 3
        3: 6626:                SV **restrict el = av_fetch(av, i, 0);
call    0 returned 3
        3: 6627:                if (el && SvOK(*el) && looks_like_number(*el)) {
call    0 returned 3
        3: 6628:                    ri[valid_n].val = SvNV(*el);
call    0 returned 3
        3: 6629:                    ri[valid_n].idx = group_id;   /* group identity */
call    0 returned 3
        3: 6630:                    valid_n++;
call    0 returned 3
        3: 6631:                }
call    0 returned 3
        3: 6632:            }
call    0 returned 3
        3: 6633:            group_id++;
call    0 returned 3
        3: 6634:        }
call    0 returned 3
        3: 6635:        k = group_id;   /* number of unique groups = number of hash keys */
call    0 returned 3
        3: 6636:
call    0 returned 3
        3: 6637:    /* ------------------------------------------------------------------ */
call    0 returned 3
        3: 6638:    /* 4b. Original x / g array-pair input path                           */
call    0 returned 3
        -: 6639:    /* ------------------------------------------------------------------ */
        -: 6640:    } else {
        -: 6641:        if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 6642:            croak("kruskal_test: 'x' is a required argument and must be an ARRAY reference");
        -: 6643:        if (!g_sv || !SvROK(g_sv) || SvTYPE(SvRV(g_sv)) != SVt_PVAV)
        -: 6644:            croak("kruskal_test: 'g' is a required argument and must be an ARRAY reference");
        -: 6645:
        3: 6646:        AV *restrict x_av = (AV*)SvRV(x_sv);
call    0 returned 3
        -: 6647:        AV *restrict g_av = (AV*)SvRV(g_sv);
        3: 6648:        size_t nx = (size_t)(av_len(x_av) + 1);
        -: 6649:        size_t ng = (size_t)(av_len(g_av) + 1);
        -: 6650:        if (nx != ng) croak("kruskal_test: 'x' and 'g' must have the same length");
        -: 6651:        if (nx < 2)   croak("not enough observations");
        -: 6652:
        -: 6653:        ri = (RankInfo *)safemalloc(nx * sizeof(RankInfo));
        -: 6654:        group_names = (char **)safecalloc(nx, sizeof(char*)); // Upper bound
        -: 6655:
        -: 6656:        // Map string group names → contiguous integer IDs
        -: 6657:        HV    *restrict group_map    = newHV();
        -: 6658:        size_t          next_group_id = 0;
        -: 6659:
        -: 6660:        for (size_t i = 0; i < nx; i++) {
        -: 6661:            SV **restrict x_el = av_fetch(x_av, i, 0);
        -: 6662:            SV **restrict g_el = av_fetch(g_av, i, 0);
        -: 6663:            if (x_el && SvOK(*x_el) && looks_like_number(*x_el)
        -: 6664:                     && g_el && SvOK(*g_el)) {
        -: 6665:                const char *restrict g_str = SvPV_nolen(*g_el);
        -: 6666:                STRLEN               glen  = strlen(g_str);
        -: 6667:                SV   **restrict id_sv = hv_fetch(group_map, g_str, glen, 0);
        -: 6668:                size_t group_id;
        -: 6669:                if (id_sv) {
        -: 6670:                    group_id = SvUV(*id_sv);
        -: 6671:                } else {
        -: 6672:                    group_id = next_group_id++;
        -: 6673:                    hv_store(group_map, g_str, glen, newSVuv(group_id), 0);
        -: 6674:                    group_names[group_id] = savepvn(g_str, glen); // Save string key
        -: 6675:                }
        -: 6676:                ri[valid_n].val = SvNV(*x_el);
        -: 6677:                ri[valid_n].idx = group_id;
        -: 6678:                valid_n++;
        -: 6679:            }
        -: 6680:        }
        -: 6681:        k = next_group_id;
        -: 6682:        SvREFCNT_dec(group_map);
        -: 6683:    }
        -: 6684:
        -: 6685:    /* ------------------------------------------------------------------ */
        -: 6686:    /* 5. Shared post-extraction validation                               */
        -: 6687:    /* ------------------------------------------------------------------ */
        -: 6688:    if (valid_n < 2 || k < 2) { 
        -: 6689:        Safefree(ri); 
        -: 6690:        if (group_names) {
        -: 6691:            for (size_t i = 0; i < k; i++) { if (group_names[i]) Safefree(group_names[i]); }
        -: 6692:            Safefree(group_names);
        -: 6693:        }
        -: 6694:        if (valid_n < 2) croak("not enough observations");
        -: 6695:        croak("all observations are in the same group");
        -: 6696:    }
        -: 6697:
        -: 6698:    // 6. Ranking and Tie Accumulation (Reusing LikeR Helper)
        -: 6699:    bool   has_ties = 0;
        -: 6700:    double tie_adj  = rank_and_count_ties(ri, valid_n, &has_ties);
        -: 6701:
        -: 6702:    // 7. Aggregate Sum of Ranks AND Actual Values by Group
        -: 6703:    double *restrict group_rank_sums = (double *)safecalloc(k, sizeof(double));
        -: 6704:    double *restrict group_val_sums  = (double *)safecalloc(k, sizeof(double)); // For Mean
        -: 6705:    size_t *restrict group_counts    = (size_t *)safecalloc(k, sizeof(size_t));
        -: 6706:
        -: 6707:    for (size_t i = 0; i < valid_n; i++) {
        -: 6708:        size_t g_id = ri[i].idx;
        -: 6709:        group_rank_sums[g_id] += ri[i].rank;
        -: 6710:        group_val_sums[g_id]  += ri[i].val;
        -: 6711:        group_counts[g_id]++;
        -: 6712:    }
        -: 6713:
        -: 6714:    // 8. Calculate STATISTIC
        -: 6715:    double stat_base = 0.0;
        -: 6716:    for (size_t i = 0; i < k; i++) {
        -: 6717:        if (group_counts[i] > 0)
        -: 6718:            stat_base += (group_rank_sums[i] * group_rank_sums[i])
        -: 6719:                         / (double)group_counts[i];
        -: 6720:    }
        -: 6721:
        -: 6722:    double n_d  = (double)valid_n;
        -: 6723:    double stat = (12.0 * stat_base / (n_d * (n_d + 1.0))) - 3.0 * (n_d + 1.0);
        -: 6724:    if (tie_adj > 0.0) {
        -: 6725:        double tie_denom = 1.0 - (tie_adj / (n_d * n_d * n_d - n_d));
        -: 6726:        stat /= tie_denom;
        -: 6727:    }
        -: 6728:    int    df    = (int)k - 1;
        -: 6729:    double p_val = get_p_value(stat, df);
        -: 6730:
        -: 6731:    // 9. Return structured data exactly like R's htest
        -: 6732:    HV *restrict res = newHV();
        -: 6733:    hv_stores(res, "statistic", newSVnv(stat));
        -: 6734:    hv_stores(res, "parameter", newSViv(df));
        -: 6735:    hv_stores(res, "p_value",   newSVnv(p_val));
        -: 6736:    hv_stores(res, "p.value",   newSVnv(p_val));
        -: 6737:    hv_stores(res, "method",    newSVpv("Kruskal-Wallis rank sum test", 0));
        -: 6738:
        -: 6739:    // 10. Build the group_stats hash
        -: 6740:    HV *group_stats = newHV();
        -: 6741:    HV *stats_mean  = newHV();
        -: 6742:    HV *stats_size  = newHV();
        -: 6743:
        -: 6744:    for (size_t i = 0; i < k; i++) {
        -: 6745:        if (group_counts[i] > 0 && group_names[i]) {
        -: 6746:            double mean = group_val_sums[i] / (double)group_counts[i];
        -: 6747:            size_t nlen = strlen(group_names[i]);
        -: 6748:
        -: 6749:            hv_store(stats_mean, group_names[i], nlen, newSVnv(mean), 0);
        -: 6750:            hv_store(stats_size, group_names[i], nlen, newSVuv(group_counts[i]), 0);
        -: 6751:        }
        -: 6752:        if (group_names[i]) Safefree(group_names[i]); // Clean up name copy
        -: 6753:    }
        -: 6754:
        -: 6755:    // Embed the nested hashes
        -: 6756:    hv_stores(group_stats, "mean", newRV_noinc((SV*)stats_mean));
        -: 6757:    hv_stores(group_stats, "size", newRV_noinc((SV*)stats_size));
        -: 6758:    hv_stores(res, "group_stats",  newRV_noinc((SV*)group_stats));
        -: 6759:
        -: 6760:    // Memory Cleanup
        -: 6761:    Safefree(group_names);
        -: 6762:    Safefree(group_rank_sums); 
        -: 6763:    Safefree(group_val_sums);
        -: 6764:    Safefree(group_counts); 
        -: 6765:    Safefree(ri);
        -: 6766:
        -: 6767:    RETVAL = newRV_noinc((SV*)res);
        -: 6768:}
        -: 6769:#line 6770 "LikeR.c"
        -: 6770:	RETVAL = sv_2mortal(RETVAL);
        -: 6771:	ST(0) = RETVAL;
        -: 6772:    }
        -: 6773:    XSRETURN(1);
        -: 6774:}
        -: 6775:
        -: 6776:
        -: 6777:XS_EUPXS(XS_Stats__LikeR_var_test); /* prototype to pass -Wmissing-prototypes */
        -: 6778:XS_EUPXS(XS_Stats__LikeR_var_test)
        -: 6779:{
        -: 6780:    dVAR; dXSARGS;
        -: 6781:    PERL_UNUSED_VAR(cv); /* -W */
        -: 6782:    PERL_UNUSED_VAR(items); /* -W */
        -: 6783:    {
        -: 6784:	SV *	RETVAL;
        -: 6785:#line 6154 "LikeR.xs"
        -: 6786:{
        -: 6787:	SV* restrict x_sv = NULL;
        -: 6788:	SV* restrict y_sv = NULL;
        -: 6789:	double ratio = 1.0, conf_level = 0.95;
        -: 6790:	const char* restrict alternative = "two.sided";
        -: 6791:	unsigned int arg_idx = 0;
        -: 6792:
        -: 6793:	// 1. Shift positional argument 'x' if it's an array reference
        -: 6794:	if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 6795:	  x_sv = ST(arg_idx);
        -: 6796:	  arg_idx++;
        -: 6797:	}
        -: 6798:
        -: 6799:	// 2. Shift positional argument 'y' if it's an array reference
        -: 6800:	if (arg_idx < items && SvROK(ST(arg_idx)) && SvTYPE(SvRV(ST(arg_idx))) == SVt_PVAV) {
        -: 6801:	  y_sv = ST(arg_idx);
        -: 6802:	  arg_idx++;
        -: 6803:	}
        -: 6804:
        -: 6805:	// Ensure the remaining arguments form complete key-value pairs
        -: 6806:	if ((items - arg_idx) % 2 != 0) {
        -: 6807:	  croak("Usage: var_test(\\@x, \\@y, key => value, ...)");
        -: 6808:	}
        -: 6809:
        -: 6810:	// --- Parse named arguments from the remaining flat stack ---
        -: 6811:	for (; arg_idx < items; arg_idx += 2) {
        -: 6812:	  const char* restrict key = SvPV_nolen(ST(arg_idx));
        -: 6813:	  SV* restrict val = ST(arg_idx + 1);
        -: 6814:
        -: 6815:	  if      (strEQ(key, "x"))           x_sv        = val;
        -: 6816:	  else if (strEQ(key, "y"))           y_sv        = val;
        -: 6817:	  else if (strEQ(key, "ratio"))       ratio       = SvNV(val);
        -: 6818:	  else if (strEQ(key, "conf_level") || strEQ(key, "conf.level")) conf_level = SvNV(val);
        -: 6819:	  else if (strEQ(key, "alternative")) alternative = SvPV_nolen(val);
        -: 6820:	  else croak("var_test: unknown argument '%s'", key);
        -: 6821:	}
        -: 6822:
        -: 6823:	// --- Validate required inputs / types ---
        -: 6824:	if (!x_sv || !SvROK(x_sv) || SvTYPE(SvRV(x_sv)) != SVt_PVAV)
        -: 6825:	  croak("var_test: 'x' is a required argument and must be an ARRAY reference");
        -: 6826:	if (!y_sv || !SvROK(y_sv) || SvTYPE(SvRV(y_sv)) != SVt_PVAV)
        -: 6827:	  croak("var_test: 'y' is a required argument and must be an ARRAY reference");
        -: 6828:
        -: 6829:	if (ratio <= 0.0 || !isfinite(ratio)) 
        -: 6830:	  croak("var_test: 'ratio' must be a single positive number");
        -: 6831:	if (conf_level <= 0.0 || conf_level >= 1.0 || !isfinite(conf_level))
        -: 6832:	  croak("var_test: 'conf.level' must be a single number between 0 and 1");
        -: 6833:
        -: 6834:	AV* restrict x_av = (AV*)SvRV(x_sv);
        -: 6835:	AV* restrict y_av = (AV*)SvRV(y_sv);
        -: 6836:	size_t nx_raw = av_len(x_av) + 1;
        -: 6837:	size_t ny_raw = av_len(y_av) + 1;
        -: 6838:
        -: 6839:	// --- Computation via Welford's Algorithm (ignoring NaNs) ---
        -: 6840:	double mean_x = 0.0, M2_x = 0.0;
        -: 6841:	size_t nx = 0;
        -: 6842:	for (size_t i = 0; i < nx_raw; i++) {
        -: 6843:		SV** restrict tv = av_fetch(x_av, i, 0);
        -: 6844:		if (tv && SvOK(*tv) && looks_like_number(*tv)) {
        -: 6845:			double val = SvNV(*tv);
        -: 6846:			if (!isnan(val) && isfinite(val)) {
        -: 6847:				nx++;
        -: 6848:				double delta = val - mean_x;
        -: 6849:				mean_x += delta / nx;
        -: 6850:				M2_x += delta * (val - mean_x);
        -: 6851:			}
        -: 6852:		}
        -: 6853:	}
        -: 6854:
        -: 6855:	double mean_y = 0.0, M2_y = 0.0;
        -: 6856:	size_t ny = 0;
        -: 6857:	for (size_t i = 0; i < ny_raw; i++) {
        -: 6858:	  SV** restrict tv = av_fetch(y_av, i, 0);
        -: 6859:	  if (tv && SvOK(*tv) && looks_like_number(*tv)) {
        -: 6860:		   double val = SvNV(*tv);
        -: 6861:		   if (!isnan(val) && isfinite(val)) {
        -: 6862:		       ny++;
        -: 6863:		       double delta = val - mean_y;
        -: 6864:		       mean_y += delta / ny;
        -: 6865:		       M2_y += delta * (val - mean_y);
        -: 6866:		   }
        -: 6867:	  }
        -: 6868:	}
        -: 6869:
        -: 6870:	if (nx < 2) croak("not enough 'x' observations");
        -: 6871:	if (ny < 2) croak("not enough 'y' observations");
        -: 6872:
        -: 6873:	double df_x = (double)(nx - 1);
        -: 6874:	double df_y = (double)(ny - 1);
        -: 6875:	double var_x = M2_x / df_x;
        -: 6876:	double var_y = M2_y / df_y;
        -: 6877:
        -: 6878:	if (var_y == 0.0) croak("var_test: variance of 'y' is zero (cannot divide by zero)");
        -: 6879:
        -: 6880:	// --- Statistics Math ---
        -: 6881:	double estimate = var_x / var_y;
        -: 6882:	double statistic = estimate / ratio;
        -: 6883:
        -: 6884:	double p_val = pf(statistic, df_x, df_y);
        -: 6885:	double ci_lower = 0.0, ci_upper = INFINITY;
        -: 6886:
        -: 6887:	if (strcmp(alternative, "less") == 0) {
        -: 6888:	  ci_upper = estimate / qf_bisection(1.0 - conf_level, df_x, df_y);
        -: 6889:	} else if (strcmp(alternative, "greater") == 0) {
        -: 6890:	  p_val = 1.0 - p_val;
        -: 6891:	  ci_lower = estimate / qf_bisection(conf_level, df_x, df_y);
        -: 6892:	} else {
        -: 6893:	  // two.sided
        -: 6894:	  double p1 = p_val;
        -: 6895:	  double p2 = 1.0 - p_val;
        -: 6896:	  p_val = 2.0 * (p1 < p2 ? p1 : p2);
        -: 6897:
        -: 6898:	  double beta = (1.0 - conf_level) / 2.0;
        -: 6899:	  ci_lower = estimate / qf_bisection(1.0 - beta, df_x, df_y);
        -: 6900:	  ci_upper = estimate / qf_bisection(beta, df_x, df_y);
        -: 6901:	}
        -: 6902:
        -: 6903:	// --- Pack Results ---
        -: 6904:	HV* restrict results = newHV();
        -: 6905:	hv_store(results, "statistic", 9, newSVnv(statistic), 0);
        -: 6906:
        -: 6907:	AV* restrict param_av = newAV();
        -: 6908:	av_push(param_av, newSVnv(df_x));
        -: 6909:	av_push(param_av, newSVnv(df_y));
        -: 6910:	hv_store(results, "parameter", 9, newRV_noinc((SV*)param_av), 0);
        -: 6911:
        -: 6912:	hv_store(results, "p_value", 7, newSVnv(p_val), 0);
        -: 6913:
        -: 6914:	AV* restrict conf_int = newAV();
        -: 6915:	av_push(conf_int, newSVnv(ci_lower));
        -: 6916:	av_push(conf_int, newSVnv(ci_upper));
        -: 6917:	hv_store(results, "conf_int", 8, newRV_noinc((SV*)conf_int), 0);
        -: 6918:
        -: 6919:	hv_store(results, "estimate", 8, newSVnv(estimate), 0);
        -: 6920:	hv_store(results, "null_value", 10, newSVnv(ratio), 0);
        -: 6921:	hv_store(results, "alternative", 11, newSVpv(alternative, 0), 0);
        -: 6922:	hv_store(results, "method", 6, newSVpv("F test to compare two variances", 0), 0);
        -: 6923:
        -: 6924:	RETVAL = newRV_noinc((SV*)results);
        -: 6925:}
        -: 6926:#line 6927 "LikeR.c"
        -: 6927:	RETVAL = sv_2mortal(RETVAL);
        -: 6928:	ST(0) = RETVAL;
        -: 6929:    }
        -: 6930:    XSRETURN(1);
        -: 6931:}
        -: 6932:
        -: 6933:#ifdef __cplusplus
        -: 6934:extern "C" {
        -: 6935:#endif
        -: 6936:XS_EXTERNAL(boot_Stats__LikeR); /* prototype to pass -Wmissing-prototypes */
        -: 6937:XS_EXTERNAL(boot_Stats__LikeR)
        -: 6938:{
        -: 6939:#if PERL_VERSION_LE(5, 21, 5)
        -: 6940:    dVAR; dXSARGS;
        -: 6941:#else
        -: 6942:    dVAR; dXSBOOTARGSXSAPIVERCHK;
        -: 6943:#endif
        -: 6944:#if PERL_VERSION_LE(5, 8, 999) /* PERL_VERSION_LT is 5.33+ */
        -: 6945:    char* file = __FILE__;
        -: 6946:#else
        -: 6947:    const char* file = __FILE__;
        -: 6948:#endif
        -: 6949:
        -: 6950:    PERL_UNUSED_VAR(file);
        -: 6951:
        -: 6952:    PERL_UNUSED_VAR(cv); /* -W */
        -: 6953:    PERL_UNUSED_VAR(items); /* -W */
        -: 6954:#if PERL_VERSION_LE(5, 21, 5)
        -: 6955:    XS_VERSION_BOOTCHECK;
        -: 6956:#  ifdef XS_APIVERSION_BOOTCHECK
        -: 6957:    XS_APIVERSION_BOOTCHECK;
        -: 6958:#  endif
        -: 6959:#endif
        -: 6960:
        -: 6961:        newXS_deffile("Stats::LikeR::ks_test", XS_Stats__LikeR_ks_test);
        -: 6962:        newXS_deffile("Stats::LikeR::wilcox_test", XS_Stats__LikeR_wilcox_test);
        -: 6963:        newXS_deffile("Stats::LikeR::_chisq_c", XS_Stats__LikeR__chisq_c);
        -: 6964:        (void)newXSproto_portable("Stats::LikeR::write_table", XS_Stats__LikeR_write_table, file, ";@");
        -: 6965:        (void)newXSproto_portable("Stats::LikeR::_parse_csv_file", XS_Stats__LikeR__parse_csv_file, file, "$$$;$");
        -: 6966:        (void)newXSproto_portable("Stats::LikeR::cov", XS_Stats__LikeR_cov, file, "$$;$");
        -: 6967:        (void)newXSproto_portable("Stats::LikeR::glm", XS_Stats__LikeR_glm, file, ";@");
        -: 6968:        (void)newXSproto_portable("Stats::LikeR::cor_test", XS_Stats__LikeR_cor_test, file, ";@");
        -: 6969:        (void)newXSproto_portable("Stats::LikeR::shapiro_test", XS_Stats__LikeR_shapiro_test, file, "$");
        -: 6970:        (void)newXSproto_portable("Stats::LikeR::min", XS_Stats__LikeR_min, file, "@");
        -: 6971:        (void)newXSproto_portable("Stats::LikeR::max", XS_Stats__LikeR_max, file, "@");
        -: 6972:        (void)newXSproto_portable("Stats::LikeR::runif", XS_Stats__LikeR_runif, file, ";@");
        -: 6973:        (void)newXSproto_portable("Stats::LikeR::rbinom", XS_Stats__LikeR_rbinom, file, ";@");
        -: 6974:        (void)newXSproto_portable("Stats::LikeR::hist", XS_Stats__LikeR_hist, file, "$;@");
        -: 6975:        (void)newXSproto_portable("Stats::LikeR::quantile", XS_Stats__LikeR_quantile, file, ";@");
        -: 6976:        (void)newXSproto_portable("Stats::LikeR::mean", XS_Stats__LikeR_mean, file, "@");
        -: 6977:        (void)newXSproto_portable("Stats::LikeR::sum", XS_Stats__LikeR_sum, file, "@");
        -: 6978:        (void)newXSproto_portable("Stats::LikeR::sd", XS_Stats__LikeR_sd, file, "@");
        -: 6979:        (void)newXSproto_portable("Stats::LikeR::var", XS_Stats__LikeR_var, file, "@");
        -: 6980:        (void)newXSproto_portable("Stats::LikeR::t_test", XS_Stats__LikeR_t_test, file, ";@");
        -: 6981:        (void)newXSproto_portable("Stats::LikeR::p_adjust", XS_Stats__LikeR_p_adjust, file, "$;$");
        -: 6982:        (void)newXSproto_portable("Stats::LikeR::median", XS_Stats__LikeR_median, file, "@");
        -: 6983:        (void)newXSproto_portable("Stats::LikeR::cor", XS_Stats__LikeR_cor, file, "$;$$");
        -: 6984:        (void)newXSproto_portable("Stats::LikeR::scale", XS_Stats__LikeR_scale, file, "@");
        -: 6985:        (void)newXSproto_portable("Stats::LikeR::matrix", XS_Stats__LikeR_matrix, file, ";@");
        -: 6986:        (void)newXSproto_portable("Stats::LikeR::lm", XS_Stats__LikeR_lm, file, ";@");
        -: 6987:        (void)newXSproto_portable("Stats::LikeR::seq", XS_Stats__LikeR_seq, file, "$$;$");
        -: 6988:        (void)newXSproto_portable("Stats::LikeR::rnorm", XS_Stats__LikeR_rnorm, file, ";@");
        -: 6989:        (void)newXSproto_portable("Stats::LikeR::aov", XS_Stats__LikeR_aov, file, "$$");
        -: 6990:        newXS_deffile("Stats::LikeR::fisher_test", XS_Stats__LikeR_fisher_test);
        -: 6991:        newXS_deffile("Stats::LikeR::power_t_test", XS_Stats__LikeR_power_t_test);
        -: 6992:        newXS_deffile("Stats::LikeR::kruskal_test", XS_Stats__LikeR_kruskal_test);
        -: 6993:        newXS_deffile("Stats::LikeR::var_test", XS_Stats__LikeR_var_test);
        -: 6994:#if PERL_VERSION_LE(5, 21, 5)
        -: 6995:#  if PERL_VERSION_GE(5, 9, 0)
        -: 6996:    if (PL_unitcheckav)
        -: 6997:        call_list(PL_scopestack_ix, PL_unitcheckav);
        -: 6998:#  endif
        -: 6999:    XSRETURN_YES;
        -: 7000:#else
        -: 7001:    Perl_xs_boot_epilog(aTHX_ ax);
        -: 7002:#endif
        -: 7003:}
        -: 7004:
        -: 7005:#ifdef __cplusplus
        -: 7006:}
        -: 7007:#endif
