NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
NuGet · #2439 most downloaded on NuGet
Pragmatic Statistical Toolkit
Last release 2 months ago
26 Jul 2026
Release timing varies
gaps range from 8 days to 3 months
Rarely documented
notes for 5 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
1 years old
67 releases · first in 2025
One column per month.
Documentation and tooling release. No library code changed: every estimator returns what it returned in v14.0.0, and all 944 reference test files are
Documentation and tooling release. No library code changed: every estimator returns what it returned in v14.0.0, and all 944 reference test files are identical across the eight implementations. The changes are five incorrect statements in the manual, one new assumption section, and fixes to the toolchain that renders the manual.
Minimum achievable misrate thresholds, off by one sample. The one-sample floor is $2^{1-n}$, so $10^{-6}$ requires $n \geq 21$ rather than 20, since $2^{-19} = 1.9 \cdot 10^{-6}$ remains above the target. Likewise $10^{-3}$ requires $n \geq 11$ rather than 10. The previous claim contradicted both the table printed beside it and the methodology chapter.
Asymptotic efficiency, attributed to both estimators equally. The 29% breakdown point yields about 95% efficiency for Center ($3/\pi$) and 86% for Spread, not 95% for both.
SignMargin, randomized cutoff described backwards. The implementation applies margin $2k_{hi}$ with probability $p$ and $2k_{lo}$ with probability $1-p$; the text had the two exchanged.
SpreadBounds, independence justified incorrectly. The text cited "the sparity assumption", which denotes Spread(x) > 0 and is unrelated; independence follows from the pairs being disjoint. It also assumed symmetry, which is unnecessary: the population spread is by definition the median of $|X_1 - X_2|$.
Ratio, defined through a term with a different meaning. "Geometric median of pairwise ratios" denotes the multivariate L1 median, and the plain median of the ratios is not what the estimator computes either: with an even number of pairs, $\exp(\mathrm{median}(\log r))$ averages the two central values geometrically. The definition is now the median of the pairwise log-ratios, exponentiated.
Domain versus informative bounds. misrate >= misrate_min is the accepted domain, but at exact equality both margin functions return 0 and the bounds span the full range. The two statements are now separate: the domain permits equality, informative bounds require misrate > misrate_min.
ShiftBounds and RatioBounds claimed coverage that required no assumptions about the distribution. Exact coverage for a Mann-Whitney interval requires the two distributions to share a shape and differ only in location, $F_X(t) = F_Y(t - \Delta)$; under unequal dispersion the actual coverage drifts from $1 - \mathrm{misrate}$.
Weak Shape Assumption states the model, and ShiftBounds, RatioBounds and DisparityBounds reference it, matching how CenterBounds references weak symmetry. AvgSpreadBounds is unaffected: it combines two distribution-free SpreadBounds through a Bonferroni split.
The Typst-to-MDX generator dropped characters without reporting them. Shorthand and SmartQuote are leaf nodes that the converter passed over, so the web manual lost 96 em dashes, 244 quotes and apostrophes, two en dashes, and one minus sign that rendered "bounds around −2" as "bounds around 2". A separate off-by-one in #include handling left stray quote characters that split 96 paragraphs. An unresolved cross-reference now fails the build instead of degrading the link to plain text.
The KaTeX stylesheet and fonts are bundled from the dependency rather than fetched from a CDN, which removes a runtime external request and puts the version under dependency tooling. Emitting woff2 alone reduces the asset payload by 72%, from 1144032 to 324718 bytes.
The manual also uses an en dash consistently in compound proper names: Hodges–Lehmann, Mann–Whitney, Fisher–Yates, Box–Muller, Lindeberg–Feller.
Astro 5 to 7 and @astrojs/mdx 4 to 7, upgraded together because each mdx major pins one astro major in its peer range. All 44 pages render pixel-identical; the build runs through rolldown and takes roughly half as long.
Toolchain versions are pinned explicitly instead of resolving to latest or stable: typst 0.15.1, rust 1.97.1, golangci-lint 2.12.2. The typst pin is paired with typst-syntax in the generator, which parses the same sources the binary renders. Four version declarations that could drift independently were reduced to one source each: KaTeX (three places), @types/node against the pinned Node runtime, the go directive against the mise pin, and ruff against a pip copy that shadowed it.
Clippy, rustfmt, the generator test suite and a generated-artifact drift check now run in CI rather than only locally. img/logo.png had carried a pre-January design since the SVG gradients changed without regeneration; it is regenerated and covered by the drift check.
The published packages differ from v14.0.0 only in version metadata, plus the go directive in go/go.mod raised from 1.25 to 1.26 to match the toolchain that builds and tests the module.
Full Changelog: v14.0.0...v14.0.1
This release is about correctness and currency. Five cross-language correctness fixes make the estimators return the right number on more inputs and b
This release is about correctness and currency. Five cross-language correctness fixes make the estimators return the right number on more inputs and behave identically across all seven implementations: overflow no longer corrupts spread on large samples or shift/ratio bounds near the floating-point ceiling, the pairwise-margin binomial is now computed with exact integer arithmetic instead of drifting in floating point, C#'s center/spread seed their randomness deterministically from the input like the other six ports, and uniform_int is exact for large ranges with a single, explicit overflow contract everywhere. Alongside the fixes, every language's toolchain moves to a current baseline, and the manual gains an opening Introduction chapter. The correctness fixes and the raised runtime floors are breaking; the migration guide below covers every change per language.
spread no longer overflows its internal counter on samples of 65537+ elements; shift and ratio bounds no longer overflow to infinity on finite inputs near the floating-point ceiling; uniform_int is now exact for large ranges. Every in-range result that was already correct is unchanged.pairwise_margin is now computed with exact integer arithmetic in every language (a native 64-bit helper in R, whose built-in choose() is inexact even below 2^53). Margins that previously drifted — e.g. 784 where the correct value is 782 — are now exact and identical across all seven ports.center and spread now seed their internal RNG from the input via FNV-1a, matching the other six ports byte-for-byte. Previously C# fell back to a clock-seeded System.Random, so its results were non-deterministic and could disagree with the other languages.uniform_int overflow contract. All seven ports now reject ranges larger than 2^52 — the largest range a double-based implementation can sample exactly — with a language-native error, instead of the previous inconsistent behavior.Compare1/Compare2 against practical thresholds, with a canonical lowercase verdict.Callers on the typed Sample API with default arguments need no source changes except where a raised runtime floor or a removed C# member applies. Two estimators return different (still deterministic) numbers by design — see "Reference values" below; assert against the shipped reference fixtures rather than hardcoded literals.
The minimum supported runtime rises for four languages and is unchanged for the other three:
| Language | v13 | v14 | Action |
|---|---|---|---|
| Python | 3.8 | 3.12 | Upgrade the interpreter |
| Go | 1.20 | 1.25 | Raise the module's Go version |
| Rust | edition 2021 | edition 2024 (MSRV 1.87) | Use a Rust toolchain >= 1.87 |
| Node (TypeScript) | — | >= 22 (target ES2023) | Upgrade Node |
| C# | netstandard2.0;net6.0 |
netstandard2.0;net6.0;net10.0 |
None — floor unchanged, net10.0 added |
| Kotlin | JVM 11 | JVM 11 | None — only the build toolchain (Kotlin 2.4) moved |
| R | — | — | None |
uniform_int overflow contract (all languages)uniform_int / UniformInt64 now throws when the requested range exceeds 2^52 (4503599627370496), using the language-native error type: OverflowError (Python), RangeError (TypeScript), OverflowException (C#), ArithmeticException (Kotlin), a panic (Go, Rust), and stop() (R). Previously the threshold was inconsistent across ports — Go/Python/TypeScript accepted ranges up to 2^64 but returned imprecise values above 2^52, while Rust/C#/Kotlin threw only at i64 overflow. 2^52 is the largest range any implementation can sample exactly; ranges this large are far beyond any real sample. If you relied on the old behavior, clamp your range to <= 2^52.
Two fixes change numeric outputs deterministically:
pairwise_margin now uses exact integer arithmetic, correcting a handful of previously-drifting margins (e.g. 784 -> 782). All seven languages now agree bit-for-bit.center / spread seed their RNG from the input instead of a clock, so their results are now deterministic and match the other six ports. If you captured C# outputs from v13, regenerate them.If you assert against these estimators, compare against the shipped reference fixtures rather than pinned literals.
Estimate dropped its Random? parameter. CenterImpl.Estimate(values, Random? random = null, bool assumeSorted = false) is now Estimate(values, bool assumeSorted = false) (same for SpreadImpl); randomness is derived deterministically from the input. Remove the argument from any call site that passed one — call sites using defaults are unaffected.WeightedSampleNotSupportedException (the obsolete SerializationInfo/StreamingContext ctor, SYSLIB0051). Binary serialization of exceptions is dead in modern .NET; no replacement is needed.net10.0 target was added; net6.0 and netstandard2.0 remain, so existing consumers do not need to change target framework.ComparisonVerdict gains toId(), returning the canonical lowercase identifier ("greater" / "less" / "inconclusive") that matches the other ports and the reference JSON. This is an addition; existing code is unaffected.
No source-level API changes beyond the runtime floor (above) and the shared uniform_int overflow contract. The pairwise-margin correction may change a margin value if you pinned one; use the reference fixtures.
Upgrading from 12.1.0 therefore brings the full set of 13.0.0 breaking changes; see the v13.0.0 release notes for the per-language migration guide.
A patch release. The only change is a CI fix: v13.0.0 failed to publish to the npm registry, so no library code or public API changed since v13.0.0.
npm: 13.0.1 is the first 13.x release on npm — v13.0.0 never reached the registry. Upgrading from 12.1.0 therefore brings the full set of 13.0.0 breaking changes; see the v13.0.0 release notes for the per-language migration guide.
crates.io, NuGet, PyPI, Maven Central, Go, GitHub: unchanged from v13.0.0 — 13.0.1 is behavior-identical and needs no action.
Full Changelog: v13.0.0...v13.0.1
…the migration guide below covers every breaking change per language.
This release makes the estimator kernels' sorted-input contract explicit and safe across all seven language implementations. The internal Hodges-Lehmann (center), spread, shift, and their bounds only ever run on sorted data, but that precondition used to be implicit and unguarded. It is now a first-class part of the API: every implementation gains a raw native-array entry point with an assume_sorted fast path, the central pairwise midpoints are computed overflow-safely, the selection loops are bounded so misuse fails fast instead of hanging, and Sample is defensively immutable where it was not. These improvements came with public API changes in every language. The typed Sample-based API stays the recommended entry point and is largely unchanged; the migration guide below covers every breaking change per language.
Sample.assume_sorted fast path. Passing already-ascending data skips the internal sort; the typed Sample API uses a cached sorted view, so repeated estimators on the same sample sort only once.center and spread no longer overflow to infinity on finite inputs near the floating-point ceiling; every in-range result is bit-identical to before.assume_sorted on unsorted data fails with a deterministic convergence error instead of spinning forever.Sample. In C#, Go, Python, and TypeScript, Sample copies its input on construction and no longer hands out a mutable view of its internal buffers.Nothing needs to change for callers who stay on the typed Sample API and its default arguments, except in Kotlin and C# where a value-type change forces a recompile (details below). Everything else is opt-in surface that only affected code must touch.
Pragmastat.Algorithms.FastShift / FastRatio removed. Both public classes became internal (ShiftImpl / RatioImpl). Use the new native-array overloads for the median shift/ratio; there is no public replacement for arbitrary pairwise-quantile arrays.
// before
double s = FastShift.Estimate(x, y, new[] { 0.5 })[0];
// after
double s = Toolkit.Shift(x, y); // new double[] overloadProbability.Zero / Half / One / NaN removed. Construct the value instead. NaN has no replacement — a NaN probability is now rejected (see below).
Probability.Half // before
(Probability)0.5 // afterProbability from NaN now throws. new Probability(double.NaN), the implicit double→Probability conversion, and Probability.Of(double.NaN) now throw ArgumentOutOfRangeException instead of storing a NaN. Filter NaN misrate/probability values before passing them in. In-range values are unaffected.
Sample constructors dropped the optional Subject? validationSubject. Remove the argument. The two IReadOnlyList<double>-based constructors are binary-incompatible for all callers (C# binds optional-argument call sites to the full signature), so recompile against 13.0.0.
new Sample(values, unit, Subject.X) // before
new Sample(values, unit) // after
new Sample(values, weights, unit, Subject.X) // before
new Sample(values, weights, unit) // afterMinor: on a degenerate single-element sample, SpreadBounds may now report the Sparity violation where it previously reported Domain (the input was already an error either way; only matters if you branch on the violation subject).
Package-level estimator functions became the raw-slice fast path. Center, Spread, Shift, Ratio, Disparity, all *Bounds, and SpreadBoundsWithSeed / DisparityBoundsWithSeed changed from taking *Sample and returning Measurement to taking []float64 plus a required assumeSorted bool and returning float64 / Bounds. The identically-named Sample methods kept their exact signatures — switch to those to preserve behavior and the measurement unit.
// before
m, err := pragmastat.Center(sample)
b, err := pragmastat.ShiftBounds(sx, sy, 1e-3)
// after (recommended: method form, unit preserved)
m, err := sample.Center()
b, err := sx.ShiftBounds(sy, 1e-3)
// or the raw path (returns float64 / NumberUnit-tagged Bounds)
v, err := pragmastat.Center(sample.Values(), false)SpreadBoundsWithSeed / DisparityBoundsWithSeed now also exist as Sample methods (sample.SpreadBoundsWithSeed(1e-3, "seed")); the raw free functions gained the trailing assumeSorted argument.
Sample fields became accessor methods. Values, Weights, Unit, IsWeighted, TotalWeight, WeightedSize are now methods. Append (). Values() / Weights() now return a defensive copy, so mutating the result no longer mutates the sample.
u := sample.Unit // before
u := sample.Unit() // afterThe typed misrate is now a Probability value class, not Double. This affects the Threshold data class and the five Sample-based bounds functions (centerBounds, spreadBounds, shiftBounds, ratioBounds, disparityBounds). Wrap the rate at construction and unwrap at read. Because Probability is a @JvmInline value class, the change is binary-incompatible for all callers (including those that omit the misrate) — recompile against 13.0.0.
// before
centerBounds(sample, 0.05)
Threshold(Metric.Center, value, 0.01)
val d: Double = threshold.misrate
// after
centerBounds(sample, Probability(0.05))
Threshold(Metric.Center, value, Probability(0.01))
val d: Double = threshold.misrate.valueCalls that omit the misrate (centerBounds(sample)) still compile unchanged at source level. Destructuring a Threshold now yields a Probability for the third component, and copy(misrate = Probability(...)) must wrap.
Minor: on a single-element sample, spreadBounds now throws AssumptionId.SPARITY where it previously threw DOMAIN (subject stays X); only matters if you catch AssumptionException and branch on the violation id.
Sample.log() removed. Rebuild from logged values.
ls = sample.log() # before
import numpy as np
ls = Sample(np.log(sample.values)) # afterNote: the old method ran a positivity pre-check; np.log of a non-positive value yields nan/-inf and the Sample constructor then raises a validity error. Guard positivity yourself if you relied on that specific error.
Sample arrays are copied and frozen. values, weights, and sorted_values are now read-only NumPy arrays, and construction copies its input (no more aliasing the caller's array). In-place mutation raises ValueError: assignment destination is read-only. Copy first.
s.values[0] = 42.0 # before (worked)
v = s.values.copy() # after
v[0] = 42.0Point estimators' return type widened. center, spread, shift, ratio, disparity are now annotated Measurement | float (to support native-array input). Runtime is unchanged — a Sample argument still returns a Measurement; only static type-checkers (mypy/pyright) see the difference. Narrow the result if a checker complains:
m = center(s)
assert isinstance(m, Measurement) # or cast(Measurement, center(s))Sample$new() dropped the subject argument; with_subject() and sample$subject are gone. The Sample R6 class no longer carries a subject label — estimators assign the "x"/"y" role by argument position, so standard error messages are preserved without any action.
Sample$new(x, subject = "y") # before -> error: unused argument
Sample$new(x) # after
s2 <- s$with_subject("y") # before -> removed; drop the call
lbl <- s$subject # before -> now silently returns NULLThe last one is the trap: reading s$subject no longer errors, it returns NULL. If you branched on it, key off argument position at the call site instead.
Every estimators::raw::* function gained a required trailing assume_sorted: bool. This covers center, spread, shift, ratio, disparity, all *_bounds, and the seeded variants. Rust has no default arguments, so each call site must add the argument. v12.1.0 always sorted internally — pass false to reproduce the old behavior:
raw::center(&x) // before
raw::center(&x, false) // after
raw::shift_bounds(&x, &y, m) // before
raw::shift_bounds(&x, &y, m, false) // afterPass true only when the input is already ascending-sorted. The typed Sample-based estimators (pragmastat::center(&sample), …) are unchanged. Bonus: Sample is now Send + Sync, so it can be shared across threads.
Sample.sortedValues narrowed from number[] to readonly number[]. Compile-time only; the emitted JS is unchanged. If you assign it to a mutable number[] or call a mutating method on it, copy first:
const sorted: number[] = sample.sortedValues; // before
const sorted: number[] = [...sample.sortedValues]; // after
// or widen the annotation for read-only use:
const sorted: readonly number[] = sample.sortedValues;Full Changelog: v12.1.0...v13.0.0
fix: prevent Loeffler loop hang caused by int64 overflow for n+m in [62, 64] by @egor-click in #51
Full Changelog: v12.0.1...v12.1.0
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →