cribler
Batteries-included statistical randomness-test toolkit for Rust.
cribler re-exports every engine from
cribler_core (the
dependency-minimal, closure-based foundation) and adds ergonomic named-case
batching (XxxSuite), a whole-battery aggregator (Suite), and optional
rand_core/urng integration. Suite::run/XxxSuite::run return
hashbrown::HashMap rather than
std::collections::HashMap.
No RNG library is a hard dependency by default: every engine takes a plain
FnMut() -> f64 / FnMut() -> u64 sampler closure, so any generator — a
hand-rolled PRNG, urng, or anything
exposing rand_core::Rng — plugs in directly without enabling any feature.
Enable the rand or urng feature for pre-built typed convenience on top of
that.
Engines
| Engine | What it detects |
|---|---|
Chi-squared (ChiSq) |
Uniformity of the output distribution |
Monte Carlo pi (McPi) |
2D uniformity sanity check via pi estimation |
Serial (Serial) |
Correlation between consecutive outputs (multi-dimensional chi-squared) |
Runs (Runs) |
Sequential correlation via monotonic run lengths |
Kolmogorov-Smirnov (Ks) |
Deviation of the empirical CDF from the ideal uniform CDF |
Birthday spacing (Birthday) |
Short periods / lattice structure (Marsaglia's DIEHARD test) |
NIST SP 800-22 (Nist) |
Frequency, Block Frequency, Runs, Longest-Run-of-Ones, Cumulative Sums (bit-level subset) |
Paranoid (Paranoid) |
Elevates any single test into a battery-level test (NIST SP 800-22 §4.2) |
Quick start
use ;
// Any FnMut() -> f64 works; this one is a tiny inline xorshift32.
let mut state: u32 = 1;
let mut sampler = ;
let result = run_chisq.unwrap;
println!;
Running multiple named cases (XxxSuite)
use ChiSqSuite;
let mut state1: u32 = 1;
let mut state2: u32 = 2;
let mut next = ;
let mut suite = new;
suite.add_sampler.unwrap;
suite.add_sampler.unwrap;
for in suite.run.unwrap
Running the whole battery at once (Suite)
[Suite] wraps one ChiSqSuite/McPiSuite/SerialSuite/RunsSuite/
KsSuite/BirthdaySuite/NistSuite each, all sharing the same seed, so
registering a generator once runs every engine against it. Suite::run
returns one [SuiteResult] (all seven engines' results) per registered
generator, keyed by its case name (HashMap<String, SuiteResult>):
use ;
;
let results = new.from_custom.unwrap.run.unwrap;
let result = &results;
println!;
println!;
Suite also has from_urng32/from_urng64 (feature urng) and from_rand
(feature rand), mirroring XxxSuite — the only difference is from_custom
requires S: Clone, since each engine needs its own independent generator
instance.
Elevating a test to battery level (Paranoid)
A single p-value can pass by chance even for a bad generator. Paranoid
re-runs a test many times over independent samples and checks both the
pass proportion and the uniformity of the resulting p-values
(NIST SP 800-22 §4.2):
use ;
let mut trial = ;
let result = run_paranoid.unwrap;
println!;
Suite::from_urng32/from_urng64/from_rand/from_custom
Every XxxSuite is created with a seed — ChiSqSuite::new(seed) — and has
methods that register a named test case from a generator seeded from it:
from_urng32::<R>()/from_urng64::<R>()— aurng::Rng32/Rng64R, constructed from the suite's seed via [UrngSeed32]/[UrngSeed64] (featureurng). No instance to pass in.from_rand::<R>()— arand_core::RngR, constructed viaSeedableRng::seed_from_u64from the suite's seed (featurerand). No instance to pass in.from_custom(source)— anything else. Custom generators are always integer-based, never float-based: implementFrom<YourType> for WordSourcedirectly and pass an instance through (the suite's seed isn't involved; see the [source] module docs for a full example). On the[0, 1)-sampling engines, the rawu64draws are converted withunit_f64_from_u32/unit_f64_from_u64depending onword_bits.
The case name is taken from the generator's type name (R's name for
from_urng32/from_urng64/from_rand, or whatever you pass to
WordSource::new for from_custom).
rand feature
[]
= { = "0.1", = ["rand"] }
use ChiSqSuite;
let results = new
.
.unwrap
.run
.unwrap;
urng feature
[]
= { = "0.1", = ["urng"] }
use ChiSqSuite;
use Sfc32;
let results = new
.
.unwrap
.run
.unwrap;
println!;
Chain multiple generator types onto the same Suite before calling run()
to batch them under a shared config (each type contributes one named case,
so generators of the same type need telling apart some other way — e.g. by
going through from_custom with an explicit name instead):
use ChiSqSuite;
use ;
let results = new
..unwrap
..unwrap
.run
.unwrap;
assert_eq!;
No feature flag is required at all if you'd rather skip the typed bridges:
every engine also takes a plain closure, so
|| cribler_core::unit_f64_from_u32(rng.nextu()) works without any extra
dependency.
Note: cribler depends on urng here (not the other way around) — urng
itself has no dependency on cribler, which is what makes this feature
possible without a cyclic package dependency.
serde feature
Enable serde to make every Config/Verdict/Result type (from both
cribler_core and cribler, including SuiteResult) Serialize/
Deserialize:
[]
= { = "0.1", = ["serde", "urng"] }
use ChiSqSuite;
use Sfc32;
let results = new..unwrap.run.unwrap;
let json = to_string.unwrap;
let round_tripped: HashMap =
from_str.unwrap;
assert_eq!;
Crate layout
cribler_core: the engines themselves, with no dependency on any RNG crate. Use this directly for the smallest possible dependency footprint.cribler(this crate):cribler_coreplusSuitebatching and optionalrand_coreintegration.
License
MIT