0081 the stats numerical layer stays internal - CyrilB1531/lodestar GitHub Wiki
0081 โ The stats numerical layer stays internal
Status: accepted ยท Date: 2026-09-05
Context
Lodestar.Stats (#442) reproduces scipy.stats 1.18.0 across ten families of hypothesis test
without carrying a numerical dependency: every tail probability a test needs โ the Student and
Welch t-distributions, the chi-square and F distributions, the incomplete beta and incomplete
gamma functions underneath them, the normal CDF, the finite-sample Kolmogorov distribution the
two-sample KS test consults โ is computed by Lodestar.Stats.Internal (Beta, Gamma,
Normal, Kolmogorov, RankDistributions), not by a package this one references.
That layer is capable enough to be useful on its own: an incomplete beta function, a log-gamma,
and an erfc accurate into the far tail are exactly what a caller reaching for a p-value from a
statistic it already has would want. Three ways of exposing it were on the table, and only one
was taken.
Decision
The numerical layer stays internal, reachable only through the ten test families that already
use it. No public surface, no reference page, no sample reference.
Refused: a public Lodestar.Stats.Special namespace. Publishing Beta.RegularizedIncomplete,
Gamma.RegularizedQ, Normal.Erfc and the rest as their own namespace would make each
one a parity promise in its own right โ its own reference page under the packaging gate, its own
line in docs/wiki-map.json's covered table, its own use in samples/Lodestar.DocSnippets to
satisfy the packaging gate, and its own oracle corpus checked at the tolerance a general-purpose
special-function caller would need, not the one the ten tests above it happen to need. Issue #442
asks for ten hypothesis tests; it does not ask for a special-functions library, and a promise made
without a stated need behind it is a promise this repository would then have to keep whether or
not anyone used it. Publishing later stays possible the day a second package needs the same
functions; unpublishing a public API once shipped does not.
Refused: putting it in Lodestar.Abstractions. Lodestar.Abstractions holds CsrMatrix,
SparseNorm and the dense-block products โ the sparse primitive Lodestar.Text and
Lodestar.Decomposition both depend on as a published package floor
(src/Directory.Packages.props). Lodestar.Stats has no sparse matrix in it anywhere; putting
its incomplete beta function in Abstractions would buy a published floor between two packages
for code only one of them calls, and would widen Abstractions' own subject โ the sparse
primitive โ to include a special-functions library it was never about. The edge Stats โ Abstractions would exist for one function's convenience, not for a shared type either package's
public surface exposes.
Recorded: Normal.Erfc(x) is computed as Q(1/2, xยฒ), not a rational fit of its own. erfc
appears twice in this package โ directly, in the normal approximation every Auto/Asymptotic
rank-based test falls back to, and indirectly, since the chi-square and Student tails both reduce
to the same regularized incomplete gamma Q that a rational erfc approximation would have to
agree with independently. Deriving erfc(x) = Q(1/2, xยฒ) for x โฅ 0 from the incomplete gamma
already implemented for chi-square means there is exactly one far-tail approximation in the
package to get right, not two whose disagreement would show up as a discrepancy between, say, a
two-sided z-test and a chi-square test on the same squared statistic. The far-tail accuracy Q
already needs for a chi-square test with many degrees of freedom comes free to erfc as a
consequence, rather than being a second accuracy target chosen independently.
Recorded: the p-value tolerance is relative, and the oracle comparator was not changed to
accommodate it. tests/oracles/*.json compares floats at 1e-9 absolute everywhere else in
the repository, and a statistic (the t, the F, the U, W, H, D) lives on a scale the
data sets, so the repository's default is the right one for it. A p-value does not live on such a
scale: measured on ordinary corpus cases โ not adversarial ones โ
TTest.Independent reaches a p-value of
7.85e-26 and OneWayAnova.Test reaches
2.38e-53. At 1e-9 absolute, an
implementation whose incomplete beta or incomplete gamma returned exactly 0.0 in the far tail
would pass every one of those comparisons; the tail is exactly where a hand-written incomplete
beta or incomplete gamma is most likely to go wrong, first through underflow and then through
cancellation, so an absolute tolerance there proves nothing over the range it matters most.
StatsOracleAsserts (tests/Lodestar.Stats.Tests/Oracles/) therefore compares a p-value at
1e-9 relative and a statistic at 1e-9 absolute, in the same assertion helper, so the
two never get silently swapped.
tools/compare_oracles.py โ the Oracles are reproducible job's comparator โ was deliberately
not changed to add a per-package or per-field relative mode. Decision
0073 already chose numeric comparison at a
fixed tolerance as that job's whole contract, and decision
0079 is the precedent for refusing
to weaken an ordering or a comparison rule to serve one package's corpus rather than fixing the
corpus itself โ there the fix was a canonical sort order in the generator, not a looser diff.
Here the parallel fix is the one already in place: StatsOracleAsserts applies the relative check
inside the C# suite, where the assertion has a name and a reason attached to it, while the
reproducibility job keeps comparing the numbers the generator wrote against a fresh run of the
same generator at the one tolerance every other package's corpus is held to. Loosening
compare_oracles.py for p-values would also loosen it for every statistic already compared there,
which is a wider change than this package asked for.
Consequences
Lodestar.Stats.InternalcarriesInternalsVisibleToonly forLodestar.Stats.TestsandLodestar.Stats.NetStandard.Tests(Lodestar.Stats.csproj), the same shape every other package's internal layer uses. Nothing underLodestar.Stats.Internalappears indocs/reference/, anddocs/wiki-map.json'scoveredentry forLodestar.Statsnames only the publicLodestar.Statsnamespace.- A future package that needs an incomplete beta, an incomplete gamma, or a normal CDF makes its own case for a public special-functions surface; this decision is not a standing refusal of one, only of publishing this package's internals as a side effect of shipping ten tests.
tests/Lodestar.Stats.Tests/Oracles/StatsOracleAsserts.csis the one place the relative p-value tolerance is defined; a new stats test family uses it rather than inventing its own comparison.