A correctness release. The algorithm audit and the comprehensive-test sweeps found results that were wrong in specific classes; those are fixed below. Seeded draws are now identical on Linux, macOS and Windows and at every thread count, and CI checks this on each platform. No new statistical capability.
Installing from GitHub source now builds the same portable configuration
as CRAN (EDI_PORTABLE=1), so results match across install methods and a
compiled library no longer dies with "illegal instruction" on another CPU.
The machine-tuned build (-march=native, -O3) is opt-in with
EDI_PORTABLE=0; see "Tuning Local Builds" in the README.
Seed-pinned draws change once. Every design and resampling kernel now
shuffles with a portable Fisher-Yates on EDI's Mersenne-Twister port
instead of std::shuffle, so the same seed gives the same draws on every
platform (they differed by C++ standard library). DesignFixedGreedy,
DesignFixedGreedyDOptimal, DesignFixedMatchingGreedyPairSwitching and
DesignFixedRerandomization derive their streams from the draw index, so
draws no longer depend on the OpenMP thread count. Parametric- and
randomization-bootstrap replicate seeds are drawn without replacement.
Randomization confidence intervals (compute_rand_confidence_interval(),
compute_rand_bootstrap_confidence_interval()) invert the two-sided
p-value at alpha, giving the documented 1 - alpha level; they inverted
at alpha / 2 before and were too wide. The default pval_epsilon for
these and for the exact Zhang interval tightened from 0.005 to 1e-6.
Binary matching minimizes plain Mahalanobis or Euclidean distance, as
nbpMatching::gendistance() does, instead of squared distance, which can
change the selected pairs. match_objective = "squared_distance" on
DesignFixedBinaryMatch and DesignFixedMatchingGreedyPairSwitching
restores the old objective.
DesignSeqOneByOneKK21 and DesignSeqOneByOneKK21stepwise survival
weights come from a correct Weibull AFT fit (the previous iteration
converged to the wrong fixed point), which changes survival allocations.
InferenceSurvivalRestrictedMeanDiff truncates both arms at one shared
horizon, the smaller of the two arms' maximum follow-up.
DesignFixedOptimal bootstrap-randomization replays reuse the observed
allocation's solver and budget; the cheaper annealing profile is opt-in
through brt_solver. The old default replayed a weaker solver and the
test was conservative.
Jackknife p-values and intervals are centred on the jackknife estimate rather than the full-sample estimate.
Methods that could not give a valid answer now error instead of returning
one: InferenceIncidKKModifiedPoisson offers Wald inference only (with a
cluster-robust SE); type = "smoothed" randomization bootstrap is refused
for categorical and bounded refits, Ridit and Jonckheere-Terpstra; the
exact-test classes refuse bootstrap-weighted estimates; and
InferenceOrdinalKKCondAdjCatLogitRegr drops the Bayesian bootstrap.
DesignSeqOneByOneKK21stepwise$new(condition_on_ws_for_rand_tests = FALSE) replays the full sequential allocation in randomization tests
(exact but slow); the default keeps the fast conditional draws.
Randomization intervals for InferenceContinKKQuantileRegrOneLik and
InferencePropKKQuantileRegrOneLik work again; 1.0.2 disabled them.
Studentized Bayesian-bootstrap intervals for the Weibull, KK Weibull marginal, Weibull-frailty and modified-Poisson classes.
InferenceSurvivalKMDiff$compute_bayesian_bootstrap_two_sided_pval()
drops non-finite draws by default (na.rm = TRUE).
InferenceOrdinalRidit(reference = "treatment") returned an estimate of
0 and p = 1 for every dataset; it now reports 0.5 - mean_control_ridit
with the SE from the control arm. Ridit also misscored an interior
category absent from the reference arm.
InferenceIncidModifiedPoisson uses Zou's robust sandwich variance for
Wald inference; the model-based Poisson information gave 0.26% rejection
at a nominal 5%.
Randomization intervals for classes other than mean difference, OLS, Lin
and KK OLS reused the null distribution shifted by delta, which is only
exact for those estimators; the others now recompute. The upper-bound
refinement also moved the wrong endpoint.
InferenceAllKKWilcoxIVWC: Hodges-Lehmann SEs lacked their normal
calibration factors (about 2x too small), the bootstrap interval returned
the asymptotic one, and randomization-bootstrap draws were on a different
scale from the observed statistic. Other legacy KK *IVWC classes (hurdle
Poisson, robust regression, survival rank regression, mean difference)
used placeholder variances or dropped the pair structure when resampling,
giving NA or wrong p-values.
Mixed models: the logistic GLMM Hessian double-counted the variance
component, so SEs were wrong; logistic and Poisson GLMM and ordinal CLMM
fits multi-start over the variance scale; the KK GEE exchangeable
correlation was not divided by the dispersion (now matches geepack).
Bootstrap-weighted refits of the KK conditional-logit GLMM and the KK
ordinal mixed classes fit the wrong model (a plain GLMM, or a fixed-effects
proportional-odds model ignoring the pairs).
InferenceOrdinalCloglogRegr's parametric bootstrap: the sign mismatch
left open in 1.0.2 is fixed (the kernel stores slopes with the opposite
orientation from the shared simulator). Stereotype-logit and
continuation-ratio fits gained multi-start.
InferencePropZeroOneInflatedBetaRegr's marginal mean difference used
renormalized logistic probabilities instead of the model's softmax.
Designs: DesignSeqOneByOneRandomBlockSize redraws used the last
subject's stratum and mutated the design, and a single block_sizes value
was mis-sampled; DesignSeqOneByOnePocockSimon merged NA with a literal
"NA" level; DesignFixedOptimalBlocks could make block sizes differ by
more than one.
Incidence: the Newcombe p-value returned 1 when its root search failed
and was imprecise for small p; the exact-binomial p-value is equal-tailed
so it agrees with its Clopper-Pearson interval; the Zhang exact interval
widens its bracket and reports Inf where the bound is infinite instead
of a truncated finite value.
Survival: stratified-Cox parametric-bootstrap simulation scaled every
stratum by the global maximum; InferenceSurvivalDepCensTransform
bootstrap intervals are no longer replaced by Wald or NA based on their
sign or width; the Clayton-copula Weibull fitter overflowed at the theta
bound; the KK Weibull marginal returns NA when an arm has no events; Cox
fallbacks use Breslow ties consistently.
Stale caches: GLM, Cox and stratified-Cox design caches were keyed on w
only; log-rank and Gehan returned stale values; Design$get_X_imp()
shared memory with the raw covariates.
InferenceAllSimpleAverageDiff treats bootstrap weights as frequency
weights (it used a Kish effective sample size).
Non-estimable cases: singular information matrices give NA SEs for the
beta, NegBin, ZAP and ZINB fits instead of garbage; m-out-of-n and
subsampling selection failures record a reason and return NA; the
Jonckheere-Terpstra exact enumeration is capped at 1e6 cells and falls
back to the asymptotic p-value with a warning instead of hanging.
Crashes: GC-protection faults in the KK21 ordinal and match-data kernels;
repeated C++ compiles in InferenceRandCustom exhausted tempdir()
inodes (compiles are now cached).
Randomization intervals cache the per-permutation slopes once, and
InferenceContinOLS randomization runs in a C++ kernel. Bayesian-bootstrap
weights are drawn in one batch, ZOIB reuses a bootstrap worker, and model
matrices are built lazily.
OpenMP kernels use the package thread maximum instead of resetting the global thread count, and SIMD hints apply under Apple clang without OpenMP.
This is the first version submitted to CRAN. Versions 1.0.0 and 1.0.1 were GitHub-only releases; their entries below record what changed in each. Most of 1.0.2 is correctness work found by the extended comprehensive test suite and by writing unit tests: resampling and randomization results that were silently wrong in specific classes, a few kernel defects (including one memory-safety bug), and standard-error and confidence-interval fixes.
toggle_asserts(), set_num_cores(), and the internal thread-count
bookkeeping it drives no longer set global options() as a side effect
(CRAN policy: a package must not modify and leave changed the user's
session options). toggle_asserts() now stores its flag in the package's
own internal state; calling it no longer changes what
getOption("edi.run_asserts") returns (a user's own
options(edi.run_asserts = ...) is still read and honored). Likewise,
set_num_cores()/set_package_threads() no longer set
options(mc.cores = ...) (nothing in EDI read it back; downstream code
relying on this package to set it for parallel/pbmcapply needs to set
it itself now) and no longer call fixest::setFixest_nthreads() (which
sets options(fixest_nthreads), the user's own option, not EDI's to
change). No change to which thread counts are actually used internally —
only to these previously-observable options() side effects.InferenceCountQuasiPoisson now composes the marginal-estimand component
(MarginalEstimand), like InferenceCountPoisson, so marginal (standardized)
effect estimands are available for the quasi-Poisson class as well.Reused-worker resampling reused a stale cached fit across draws for
classes whose fit is cached under a custom guard key (first found in
InferenceOrdinalGCompMeanDiff, whose cached_values$md guard survived
the loader's narrow reset list). Every draw then returned the same
number, the resampling distribution collapsed to a point mass, and
compute_rand_two_sided_pval() sat at its floor regardless of the true
effect (reject rate 1.0 at a true null). The worker loaders for
randomization, non-parametric bootstrap, m-out-of-n bootstrap and
randomization-bootstrap draws now reset the cached fit state between
draws, and a structural regression test asserts that every class exposing
a reusable-worker method returns a non-degenerate distribution.
InferencePropGCompMeanDiff and
InferenceSurvivalGLMMWeibullFrailtyLoggammaIVWC returned NA (or a
stale value) in randomization inference: the first from a
sample-usability gate applied to the wrong resampling context, the second
from its estimate-only w_star pooling. Both are fixed and no longer
listed as known-broken.
The KK survival compound classes InferenceSurvivalKKLWACoxPHOneLik,
InferenceSurvivalGLMMWeibullFrailtyLoggammaOneLik and
InferenceSurvivalGLMMWeibullFrailtyLoggammaIVWC fed real NAs into the
fitter for every censored subject during randomization inference:
compute_treatment_estimate_during_randomization_inference() re-read y
from the design and derived dead = as.numeric(!is.na(y)), but since the
y/y_L/y_R migration the design's y uses NA to mark a censored
observation. On simulated data with a strong effect the randomization
p-value was 0.02 with no censoring and 0.97 with about 20% censoring.
y/dead are now derived as Design$get_effective_time() /
$get_effective_dead() do. A sweep of the other 16 survival classes found
no further instance.
InferenceOrdinalCloglogRegr's parametric-bootstrap p-value was
badly over-rejecting (60% at a true null). One cause is fixed: the
delta-constrained null refit started from a single cold start and could
stall; it is now multi-start (the same fix InferenceOrdinalStereotypeLogitRegr
received earlier). A second cause, a sign mismatch in the shared
bootstrap-data simulator, is root-caused but not fixed here.
The Efron biased-coin randomization null draws (generate_permutations)
compared weighted arm counts (n_T * prob_T vs n_C * (1 - prob_T))
instead of the raw arm counts DesignSeqOneByOneEfron actually compares,
so for any prob_T != 0.5 the randomization null did not follow the
design's own assignment rule. It now uses the raw counts.
fast_probit_regression's L-BFGS objective held a dangling
Eigen::Ref to a temporary, giving non-deterministic fits and NaN
negative log-likelihoods. The objective now owns its design matrix.
fast_log_binomial_regression's weighted log-likelihood let zero-weight
rows constrain the [0, 1] mean support although they are absent from
the likelihood; zero-weight rows are now skipped, so the identity- and
log-link binomial fits no longer reject valid data because of them.
The robust-regression bootstrap kernel accepted any method string and
would throw inside an OpenMP region, terminating R; method is now
validated ("M" or "MM") before the parallel region.
stable_signature() (used to key cached randomization state) hashed a
strided sample of the serialized object, which collided on 0/1 integer
permutation matrices; it now hashes the full serialization.
InferenceCountPoisson's covariance falls back to the Fisher information
when the kernel returns no X'WX; InferenceIncidLogRegr gained an
estimand-aware standard error and degrees of freedom.
Weibull and Cox fits with fixed (held) coefficients now compute the
covariance from the free-parameter information block only and expand it
back, so the free coefficients' variances are finite and the fixed
parameters' rows and columns are NaN by convention; the Cox cluster-robust
sandwich does the same.
OpenMP's primary thread no longer polls R's interrupt machinery while worker threads are active in the Wilcoxon-Hodges-Lehmann kernels.
fast_gaussian_lmm_gls_cpp() (the fixed-variance-component GLS solve used by
the randomization and non-studentised bootstrap fast paths of
InferenceContinKKGLMM) mixed X'X with a cross-term scaled by an extra
1/sigma_e^2, so it returned the wrong coefficient whenever the residual
standard deviation was not exactly 1 (on a paired-plus-singleton fixture
with sigma_e = 0.4 the slope was -0.53 instead of the correct 0.63; at
sigma_e = 1 the two agreed). It now matches an explicit GLS to machine
precision at every sigma_e.
The KK21 stepwise weight selection for ordinal responses read a
nonexistent field (ssq_b_2) from fast_ordinal_regression_with_var_cpp();
it now reads the returned ssq_b_j.
fast_zero_one_inflated_beta_cpp() did not check the length of a supplied
warm_start_params (or warm_start_fisher_info, or the row counts of X,
X_zero_one and y), so a mis-sized start let the optimizer read and write
past its parameter buffer (heap corruption, found under valgrind; it aborted
R with free(): invalid next size). It now raises an ordinary error.
InferencePropZeroOneInflatedBetaRegr always passed a correctly sized start
and was not affected.
fast_ordinal_clmm now errors on mismatched X/y/group_id lengths
instead of reading out of bounds.
InferenceIncidRiskDiff, InferenceCountRobustPoisson, and the
zero-inflated/hurdle Poisson classes reported a confident (often
zero-width) confidence interval and p-value on a perfectly or
near-perfectly fit response (e.g. y fully separated by w, or
constant), because the Huber-White/model-based standard error collapses
to a numerically-zero-but-positive value there. Found via a raw
comprehensive_tests results audit's new adversarial-data/fault-injection
sweep. robust_sandwich_variance() and the affected classes' standard-error
paths now treat a variance below .Machine$double.eps as non-estimable
(NA), not as precision.
InferenceCountHurdlePoisson/InferenceCountZeroInflatedPoisson reported
a confident estimate and p-value on a hurdle fit that has no MLE by
construction (every positive count equal to 1, so the zero-truncated
Poisson likelihood only improves as lambda -> 0). Non-estimability is
now decided from the data alone (not from what the optimizer/glmmTMB
happens to return), so the result is NA on every platform.
InferenceRandCustom$compute_rand_confidence_interval()'s fast path
ignored the trial delta shift during the randomization search, so the
returned interval was in the custom statistic's own scale (e.g. centred
on a Welch-t value of ~2-4) rather than the response's scale. The
comprehensive_tests harness's own custom-statistic randomization-CI
check also fed it a non-translation-equivariant statistic (Welch t),
which cannot give a meaningful CI under this or any correct
implementation; it now uses a difference-in-means statistic for that
check.
InferenceAllSimpleWilcox$compute_asymp_confidence_interval(alpha)
cached and returned the 95% Wilcoxon interval for every alpha, so a
90% or 99% interval silently came back at the wrong nominal level.
InferenceOrdinalAdjCatLogitRegr's parametric-bootstrap confidence
interval could be wildly wrong (e.g. [-30, -11] around an estimate of
-0.09) because each simulated replicate's treatment coefficient was
read from the wrong field of the refit object (fit$b, slopes only)
when the anchor fit's index into its own coefficient vector (fit$params,
thresholds + slopes) pointed somewhere else entirely on fit$b. Every
parametric-bootstrap replicate now reads the same field the anchor fit
used.
InferenceOrdinalContRatioRegr (and, via the same hardened
QR-column-dropping helper, 8 other ordinal threshold-model classes)
could silently fit from a garbage cold start and "converge" after one
iteration when the design matrix contained columns collinear with the
model's implicit per-stage intercept (e.g. a stratified design's two
complementary factor dummies) — qr() alone, with no intercept column
to check against, reported the matrix as full rank. The column-dropping
helper now optionally includes that implicit intercept in its rank
check.
A likelihood-ratio confidence interval whose Newton/bisection inversion
converges to within a small tolerance of the point estimate — without
landing on it exactly — was reported as a zero-width interval instead of
falling back to the Wald interval (or NA), because the "failed
inversion" sentinel check required bit-identical bounds.
InferenceOrdinalStereotypeLogitRegr's compute_lik_ratio_confidence_interval()
hit this on ~6% of one audited sample. Its
compute_lik_ratio_bootstrap_confidence_interval() had a related bug: a
bootstrap p-value below alpha at the point estimate itself (which
should be ~1, since the LR statistic there is 0) was silently treated as
a legitimate zero-width interval rather than a failed constrained-fit
refit; it now reports NA.
compute_rand_bootstrap_confidence_interval(type = "smoothed") and
compute_rand_bootstrap_two_sided_pval(type = "smoothed") added
raw-scale Gaussian kernel noise to count responses, so a resampled zero
could become a slightly negative non-integer. Under the CI inversion's
multiplicative count shift that became a large negative integer, the
Rcpp Poisson-GLMM fit gave up, and the glmmTMB fallback rejected every
such draw (GLMM FIT ERROR: negative values not allowed for the 'Poisson' family), corrupting the null distribution and yielding a degenerate
conservative bound (e.g. InferenceCountKKGLMM). With use_rcpp = FALSE
every draw failed and the p-value was NA. Kernel noise on count
responses is now rounded and floored at zero so the resampled draw stays
on the non-negative integer support (the same convention the count shift
already uses); other response types are unchanged.
InferenceCountHurdlePoisson/InferenceCountHurdleNegBin/
InferenceCountZeroInflatedPoisson/InferenceCountZeroInflatedNegBin's
standard error could fall through to the generic information-matrix-inverse
fallback on a hurdle fit with no MLE by construction (every positive count
equal to 1), and whether that generic inversion happened to return a
spurious finite value or correctly fail depended on the LAPACK/BLAS
backend (confirmed via CI to differ between this environment, where it
correctly returned non-estimable, and Windows). The data-driven
non-estimability check that already gated the estimate and p-value now
also runs before this generic fallback, so the standard error is NA on
every platform.
SimulationFramework's internal design/inference-combination builder
unconditionally disabled package assertions and never restored them; a
caller running with turn_off_asserts_for_speed = FALSE (assertions
intentionally kept on) had assertions silently disabled for the rest of
the R session after this method ran once, which could mask unrelated
argument-validation bugs in later, unrelated code in the same session. It
now restores whatever assert state was in effect on entry.
set_custom_randomization_statistic_function() and
set_custom_randomization_statistic_cpp() are removed from every concrete
estimator class. They let a bare R closure read private$des_obj_priv_int
through a hand-built environment proxy — an undocumented, fragile
mechanism that also forced every concrete estimator's own fast/vectorized
randomization-test paths to carry a guard for a feature that had nothing
to do with that estimator. Use the new InferenceRandCustom class
instead: InferenceRandCustom$new(des_obj, custom_randomization_statistic_function = function(y, w, dead) ...) (or custom_randomization_statistic_cpp =
for the same C++ source/compiled-function/RcppXPtrUtils::cppXPtr()
options as before), then call compute_rand_two_sided_pval() or
compute_rand_confidence_interval() on it. The statistic function's
calling convention is now explicit arguments — function(y, w, dead) —
not implicit access to private state; existing custom statistic functions
need this small rewrite. InferenceRandCustom has its own dedicated fast
kernel, so performance is unchanged or better than before, uniformly
across every dataset and design (previously, speed depended on which
concrete class the statistic happened to be attached to).InferenceIncidGCompRiskRatio and InferenceIncidKKGCompRiskRatio gain
subsampling and m-out-of-n bootstrap confidence intervals and two-sided
p-values (previously only available for the risk-difference gcomp
classes). The risk-ratio pivot is computed on the log scale (null at
log(delta), centered on log(estimate)), matching the convention
already used by their percentile/BCa bootstrap CIs, since the raw-scale
pivot correct for a difference is not correct for a ratio whose null is
1 and which is right-skewed at reduced effective sample size.get_local_EDI_optimization()'s hardware fingerprint now detects
CPU/RAM on Windows and macOS as well as Linux.l
whenever the lower p-value was non-significant, which is correct when
searching for a lower bound (l is the outer/conservative end there)
but wrong when searching for an upper bound, where l is the point
estimate itself and is essentially always non-significant. Every such
"conservative" upper bound silently collapsed to the point estimate,
producing badly-too-narrow intervals — confirmed by simulation at
roughly 51-61% empirical coverage instead of the nominal 95% for
InferenceContinLin, InferenceContinOLS, InferenceContinQuantileRegr,
and the KK one-likelihood classes. The fallback now keys on which bound
is being computed rather than which p-value was non-significant.SurvivalDepCensTransformSource (the dependent-censoring AFT residual
transform) protected only the treatment column from the hardened QR
column-dropping fallback, not the intercept — dropping the intercept
left a severe bias under the null (mean beta_hat around 0.71 instead
of about 0.005, coverage around 13% instead of 95%). Both columns are
now protected, the same pattern already used for the Weibull-frailty
design matrix below.InferenceSurvivalStratCoxPHRegr's score-test information closures
returned the raw Cox partial-likelihood Hessian unnegated. Since that
Hessian is negative semi-definite (a concave log-likelihood), the
"information" matrix fed to the score test was itself negative-definite,
so the shared score-test helper's positivity check always failed —
compute_score_two_sided_pval()/compute_score_confidence_interval()
returned NA on every call, regardless of formula. Fixed to negate, as
the sibling InferenceSurvivalCoxPHRegr already did.get_clogit_plus_glmm_hessian_cpp()'s exported wrapper negated an
objective that already returns the positive information matrix (not the
raw log-likelihood Hessian), so InferencePropKKGLMM and
InferenceIncidKKCondLogitGLMMIVWC/OneLik fed the score test a
negative-definite matrix — the same failure mode as the StratCoxPH bug
above, reproducing an approximately 100% NA rate for their score test
regardless of formula. The extra negation is removed.InferenceOrdinalAdjCatLogitRegr,
InferenceOrdinalCauchitRegr, InferenceOrdinalCloglogRegr,
InferenceOrdinalOrderedProbitRegr, InferenceOrdinalContRatioRegr, and
InferenceOrdinalPropOddsRegr — extracted b[length(b)] (the last
covariate's slope) as the treatment coefficient instead of b[1]
(treatment is always the design matrix's first column). The two coincide
only when the model has no covariates beyond treatment; with covariates
present, the randomization test and the Bayesian-bootstrap machinery
silently estimated and tested a different covariate's effect instead of
treatment's (confirmed via near-total null rejection on
design_formula = ~. paths).InferenceSurvivalGLMMWeibullFrailtyNormalIVWC/OneLik) fit its design
matrix with no intercept column. Without one, the control arm's baseline
log-time was pinned at 0 and the mean-zero Gaussian frailty could only
partly absorb it; the leftover bias (plus the Gumbel error's nonzero
mean) leaked into the treatment estimate, inflating Wald/score/LR/
bootstrap rejection to roughly 50-70% under the null (nominal 5%).fast_zinb.cpp) had two bugs
in its ZIP-limit reduced fit: a fixed-parameter index was off by one, so
the reduced fit pinned the parameter before the one requested (e.g.
the intercept instead of the treatment coefficient); and the ZIP-limit
score vector was one entry shorter than the full parameter vector and
was not zero-padded, so score-test consumers compared it against a
differently-sized information matrix.InferenceOrdinalKKGEE fit its GEE model on the raw, unreduced design
matrix; rank-deficient fixtures (e.g. ~0+. model matrices) made the
multgee::ordLORgee() backend refuse the fit outright, silently
swallowed to NULL (74-89% NA rates observed on affected data shapes).
It now retries through the same QR-hardened rank-reduction machinery
used elsewhere before giving up.NA
instead, starving their studentized/BCa variants of any SE:
InferenceOrdinalPropOddsRegr, InferenceCountQuasipoisson,
InferenceCountRobustPoisson, the glmmTMB-based weighted refit path
shared by the zero-augmented Poisson classes, and
InferenceAbstractKKOrdinalCLMM (the shared base of the KK ordinal
CLMM classes).InferenceProportionFractionalLogit was missing quasi-binomial
dispersion scaling, systematically overstating standard errors — 0
rejections out of several hundred simulated replicates under the true
null (near-zero power rather than nominal-level power).InferenceIncidLogBinomial's Bayesian bootstrap discarded
boundary-hitting refit replicates instead of retaining them, shrinking
the empirical spread of the bootstrap distribution — observed test size
25-36% instead of nominal 5%, and CI coverage 56-70% instead of 95%.InferenceExtPRWSubsampling was missing the finite-population
correction for without-replacement subsampling, observed at roughly
9.75% Type-I error instead of nominal 5% at a subsample fraction of
about 0.4.InferenceExtPRWSubsampling and InferenceExtMOutOfNBootstrap's
failure gate checked only the absolute count of finite replicates
(default minimum 5), not the fraction of the requested B/m. With
B in the hundreds, a high (24%+) convergence-failure rate could still
pass the gate, producing a falsely-precise, degenerate p-value. The gate
now also requires a majority of replicates to succeed.attempt$X_fit field (the correct field is attempt$fit),
so the fallback candidate was never actually generated for any class
composing that mixin (continuous/count/incidence/proportion/ordinal-KK
GEE classes).InferenceExtInformationMatrix) returned NA
for the large majority of calls from InferenceContinKKGLMM and
InferenceCountKKGLMM — their null-constrained refit's
nuisance-parameter information is positive definite only about 32% of
the time near a variance-component boundary, which is expected behavior
of the likelihood surface there, not a sign of a bad fit. This made the
score test degenerate (0% Type-I error and 0% power together, rather
than merely miscalibrated). A ridge-regularized fallback now activates
only when the unregularized path already returned a non-finite p-value.DesignFixedBlocking silently ignored user-supplied block IDs: the
randomization draw always re-derived blocks from the raw covariates
instead of using the m a caller passed at construction.InferenceSurvivalGehanWilcox and InferenceSurvivalLogRank fit their
null Cox model with the default (Efron) tie-handling, inconsistent with
the Breslow/Nelson-Aalen convention their fast C++ kernels assume — a
genuinely different martingale residual under tied event times. Both
now fit with method = "breslow" explicitly.InferenceSurvivalRestrictedMeanDiff (RMST) never populated the
treatment coefficient's SE on a full (non-estimate_only) fit, unlike
every peer class, so bootstrap callers expecting it for a studentized
pivot got NA.InferenceOrdinalPairedSignTest: an estimate is no longer NaN
whenever any single pair-difference is NA — valid pairs are now used.
Its bootstrap and jackknife distribution methods, previously disabled
with an error asserting they violate the matched-pair design
constraint, are available again.DesignSeqOneByOne and its
subclasses, except DesignSeqOneByOneBernoulli (whose assignments do
not depend on prior subjects, so row resampling is valid there). These
methods previously ran without error for sequential designs in general
and were documented as merely "conservative"; that claim did not hold
and has been removed along with the methods for the other sequential
designs.SIGABRT) instead of cleanly
interrupting, under num_cores > 1. Both checks now no-op on any
thread but the master.InferenceSurvivalCoxPHRegr's internal coxph.fit wrapper crashed
assigning column names to a 0-column design matrix (length of 'dimnames' [2] not equal to array extent) on null-model refits — e.g.
compute_lik_ratio_two_sided_pval()/compute_score_two_sided_pval()
under model_formula = ~1 — because paste0("x", seq_len(0)) returns
"x" rather than character(0). The column-naming step is now skipped
for a 0-column matrix.SimulationFramework's mirai-based parallelization
(set_num_cores(force_mirai = TRUE)) could hang indefinitely if a
daemon died before connecting. Daemon launch and every
daemon-collection call are now bounded by a deadline, with one relaunch
attempt and a clear error in place of an unbounded wait.InferenceSurvivalCoxPHRegr,
InferenceSurvivalKKLWACoxPHIVWC/OneLik, InferenceSurvivalKKStratCoxPHIVWC/OneLik,
and InferenceSurvivalStratCoxPHRegr (which already refused). The generic
randomization CI inverts an accelerated-failure-time sharp null, so its
delta axis is a log time ratio; these classes' estimates are log
hazard ratios, and the Cox model has no shape parameter linking the two.
The search was being seeded on the wrong axis and returned bounds that
were not a confidence interval for anything (on a Weibull test case:
estimate −1.70, "CI" [−1.70, −1.26]). A direct
compute_rand_confidence_interval() call now stops with an explanation,
and InferenceSuite no longer lists the method for them, as for
incidence responses. The randomization p-value and the randomization-
bootstrap CI (a percentile interval on the estimate's own scale) are
unchanged. AFT-scale survival classes (Weibull, marginal Weibull, Weibull
frailty, rank regression) are unaffected.compute_rand_confidence_interval() documents the impute-then-permute
construction (Rosenbaum 2002; Imbens & Rubin 2015) and, for survival
responses, the AFT residual construction and its censoring assumptions
(Tsiatis 1990; Wei, Ying & Lin 1990; Jin, Lin, Wei & Ying 2003).InferenceOrdinalStereotypeLogitRegr's likelihood-ratio test
(compute_lik_ratio_two_sided_pval()) now documents that it has
inflated Type-I error (roughly 18-23% vs. a nominal 5%) for this class
specifically (a non-regular case in the sense of Davies 1977), and
recommends the bootstrap (~6-7%) or Bartlett-corrected (~4%) variants
instead. The flagged method's own behavior is unchanged.InferenceSuite's combined-evidence documentation adds a "same-Y does
not mean same estimand" caveat: rows testing the same outcome under
different link functions/estimands do not share one coherent null
hypothesis under the weak (asymptotic) null, only under the
randomization sharp null — relevant to interpreting
combined_evidence$pval.Initial release of EDI (Experimental Design and Inference): a framework that pairs randomized experimental designs — fixed-sample and sequential — with inference procedures matched to each design and response type, so that estimation and testing always reflect how the data were generated.
assign_w_to_all_subjects()): DesignFixedBernoulli, DesignFixediBCRD,
DesignFixedFactorial, DesignFixedBlocking, DesignFixedCluster,
DesignFixedBlockedCluster, DesignFixedBinaryMatch,
DesignFixedMatchingGreedyPairSwitching, DesignFixedGreedy,
DesignFixedGreedyDOptimal, DesignFixedOptimal and
DesignFixedOptimalBlocks (mixed-integer-programming optimal designs via
ompr/GLPK, with simulated-annealing and greedy alternatives), and
DesignFixedRerandomization.add_one_subject_to_experiment_and_assign(), maintaining covariate
balance): DesignSeqOneByOneBernoulli, DesignSeqOneByOneiBCRD,
DesignSeqOneByOneUrn, DesignSeqOneByOneEfron (biased coin),
DesignSeqOneByOneAtkinson, DesignSeqOneByOnePocockSimon
(minimization), DesignSeqOneByOneRandomBlockSize, DesignSeqOneByOneSPBR
(stratified permuted block), and the Kapelner-Krieger matching-on-the-fly
family that builds matched pairs from the accruing subject stream:
DesignSeqOneByOneKK14, DesignSeqOneByOneKK21,
DesignSeqOneByOneKK21stepwise.ObservationalDesign,
ObservationalDesignBlocks, ObservationalDesignMatching.DesignFixedCustom, DesignCustomSequential.missRanger/missForest).Six response types, each with its own matched inference classes: continuous,
incidence (binary), count, proportion (values in [0, 1]), ordinal, and
survival — the survival response supporting exact, left-censored,
right-censored, and interval-censored observations through one y/y_L/y_R
interface.
InferenceContinOLS), Lin's covariate-interacted OLS,
quantile regression, robust (Huber) regression, and for matched (KK)
designs GLMM (InferenceContinKKGLMM), OLS/quantile/robust variants in
both IVWC (inverse-variance-weighted combination) and combined-likelihood
pooling, plus the Bai adjusted-t estimators (InferenceBaiAdjustedTKK14,
InferenceBaiAdjustedTKK21).InferenceAllSimpleAverageDiff, InferenceAllSimpleMeanDiffPooledVar,
InferenceAllSimpleWilcox, InferenceAllKKMeanDiffIVWC,
InferenceAllKKWilcoxIVWC.InferenceSuite runs all inference
classes valid for a given design/response combination and reports a single
Cauchy-combined p-value alongside the individual results.InferenceCustomAsymp,
InferenceCustomBoot, InferenceCustomRand.glmmTMB (GLMMs), fixest (fast GLMs), aftgee (rank-based AFT),
Rfit (R-estimation), survival — with EDI's own C++ kernels used
everywhere else.SimulationFramework runs Monte Carlo power, size, and
operating-characteristic studies across designs, response types, and
inference procedures, with coverage_pval/size_pval calibration
diagnostics; SimulationFrameworkReport renders results.generate_covariate_dataset() and
transform_cont_y_based_on_response_type(); optional parallelization via
mirai (set_num_cores()/unset_num_cores()).fast_* functions — typically one to three orders of magnitude
faster than the corresponding pure-R fits (see the shipped benchmark
comparisons against each canonical R baseline).tune_EDI_for_this_machine() benchmarks the
local machine across four axes and persists tuned performance-policy
defaults (get_local_EDI_optimization(),
clear_local_EDI_optimization()).get_optimization_dispatch_policy()/set_optimization_dispatch_policy()
and the corresponding *_cold_start_, *_warm_start_, and
*_parallel_dispatch_policy() pairs, plus
get_bootstrap_dispatch_policy().EDI_PORTABLE, EDI_NATIVE_SPEED, EDI_NATIVE_LTO, EDI_UNITY,
EDI_DISABLE_VECTORIZATION, EDI_DEBUG_SYMBOLS): a tuned
-march=native unity build by default locally, and a fully portable,
warning-free build for CRAN/CI (auto-selected on r-universe builders).toggle_asserts().fast_*/C++ kernel conventions);
validation evidence; and extending EDI with your own design and inference
classes (backed by the design/inference class registries and the
DesignFixedCustom/DesignCustomSequential/InferenceCustom* bases).create_model_matrix_from_features(),
robust_negbinreg(), robust_survreg(), and
robust_survreg_with_surv_object().edi_kernels (PyPI; pybind11, no R dependency).