Expand description
§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 cribler::{ChiSqConfig, run_chisq};
// Any FnMut() -> f64 works; this one is a tiny inline xorshift32.
let mut state: u32 = 1;
let mut sampler = || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state as f64 / 4294967296.0
};
let result = run_chisq("xorshift32", &mut sampler, ChiSqConfig::default()).unwrap();
println!("{result:?}");§Running multiple named cases (XxxSuite)
use cribler::ChiSqSuite;
let mut state1: u32 = 1;
let mut state2: u32 = 2;
let mut next = |state: &mut u32| -> f64 {
*state ^= *state << 13;
*state ^= *state >> 17;
*state ^= *state << 5;
*state as f64 / 4294967296.0
};
let mut suite = ChiSqSuite::new(0);
suite.add_sampler("generator-a", || next(&mut state1)).unwrap();
suite.add_sampler("generator-b", || next(&mut state2)).unwrap();
for (name, result) in suite.run().unwrap() {
println!("{name}: {:?}", result.verdict);
}§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 cribler::{Suite, WordSource};
#[derive(Clone)]
struct MyRng(u64);
impl<'a> From<MyRng> for WordSource<'a> {
fn from(mut rng: MyRng) -> Self {
WordSource::new("MyRng", 64, move || {
rng.0 = rng.0.wrapping_mul(6364136223846793005).wrapping_add(1);
rng.0
})
}
}
let results = Suite::new(1).from_custom(MyRng(1)).unwrap().run().unwrap();
let result = &results["MyRng"];
println!("{:?}", result.chisq.verdict);
println!("{:?}", result.nist.verdict);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 cribler::{ChiSqConfig, ParanoidConfig, p_value_from_z, run_chisq, run_paranoid};
let mut trial = |i: usize| {
let mut state = (i as u32 + 1).wrapping_mul(2654435761) | 1;
let mut sampler = || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state as f64 / 4294967296.0
};
let z = run_chisq(format!("trial-{i}"), &mut sampler, ChiSqConfig::default())
.unwrap()
.z_score;
p_value_from_z(z)
};
let result = run_paranoid("xorshift32.paranoid", ParanoidConfig::default(), &mut trial).unwrap();
println!("{:?}", result.verdict);§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 thesourcemodule 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
[dependencies]
cribler = { version = "0.1", features = ["rand"] }use cribler::ChiSqSuite;
let results = ChiSqSuite::new(0)
.from_rand::<rand::rngs::StdRng>()
.unwrap()
.run()
.unwrap();§urng feature
[dependencies]
cribler = { version = "0.1", features = ["urng"] }use cribler::ChiSqSuite;
use urng::Sfc32;
let results = ChiSqSuite::new(0)
.from_urng32::<Sfc32>()
.unwrap()
.run()
.unwrap();
println!("{:?}", results["Sfc32"].verdict);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 cribler::ChiSqSuite;
use urng::{Sfc32, Xorshift32};
let results = ChiSqSuite::new(0)
.from_urng32::<Sfc32>().unwrap()
.from_urng32::<Xorshift32>().unwrap()
.run()
.unwrap();
assert_eq!(results.len(), 2);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:
[dependencies]
cribler = { version = "0.1", features = ["serde", "urng"] }use cribler::ChiSqSuite;
use urng::Sfc32;
let results = ChiSqSuite::new(0).from_urng32::<Sfc32>().unwrap().run().unwrap();
let json = serde_json::to_string(&results).unwrap();
let round_tripped: hashbrown::HashMap<String, cribler::ChiSqResult> =
serde_json::from_str(&json).unwrap();
assert_eq!(results["Sfc32"].verdict, round_tripped["Sfc32"].verdict);§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
§Module map
- [
suite] /paranoid_suite: named-case batching (XxxSuite) over thecribler_coreengines.suite::Suiteaggregates every engine into one batch. source:source::WordSource(used byfrom_custom— integer-based, never float) and the [source::UrngSeed32]/[source::UrngSeed64]/[source::RandSource] traits that backfrom_urng32/from_urng64/from_rand.- [
urng_support] (featureurng): adds [urng_support::Test32]/[urng_support::Test64] (rng.run_chisq(name), …) directly to anyurng::Rng32/Rng64generator.
Re-exports§
pub use paranoid_suite::ParanoidSuite;pub use source::WordSource;pub use suite::BirthdaySuite;pub use suite::ChiSqSuite;pub use suite::KsSuite;pub use suite::McPiSuite;pub use suite::NistSuite;pub use suite::RunsSuite;pub use suite::SerialSuite;pub use suite::Suite;pub use suite::SuiteError;pub use suite::SuiteResults;
Modules§
- birthday
- Birthday spacing test engine (Marsaglia’s DIEHARD “birthday spacings” test): detects short periods and lattice structure by checking whether collisions among sampled “birthdays” occur at the expected rate.
- chisq
- Chi-squared uniformity test engine.
- internal
- Numerical helpers shared by the test engines: special functions needed to turn test statistics into p-values.
- ks
- Kolmogorov-Smirnov test engine: checks the maximum deviation between the empirical CDF of the samples and the ideal uniform CDF.
- macros
- mcpi
- Monte Carlo pi-estimation test engine.
- nist
- NIST SP 800-22 (rev. 1a) statistical test suite, bit-level subset.
- paranoid
- “Paranoid” meta-test engine: elevates any single statistical test into a battery-level test, following the “Testing Strategy and Result Interpretation” guidance of NIST SP 800-22 rev. 1a, 4.2.
- paranoid_
suite - Named-case batching for
cribler_core::run_paranoid. - runs
- Runs test engine: detects sequential correlation by analyzing the lengths of monotonic ascending/descending runs in the output sequence.
- serial
- Serial test engine: a multi-dimensional generalization of the chi-squared test that checks for correlation between consecutive outputs.
- source
- Generator sources for
Suite::from_urng32/from_urng64/from_rand/from_custom. - suite
- Named-case batching (
XxxSuite) over thecribler_coreengines. - units
- Conversions from raw generator words to
[0, 1)floats. - verdict
Macros§
Structs§
- Birthday
Config - Configuration for a birthday spacing test.
- Birthday
Result - Full result of a single birthday spacing test run.
- ChiSq
Config - Configuration for a chi-squared randomness test.
- ChiSq
Result - Full result of a single chi-squared test run.
- KsConfig
- Configuration for a Kolmogorov-Smirnov uniformity test.
- KsResult
- Full result of a single Kolmogorov-Smirnov test run.
- McPi
Config - Configuration for a Monte Carlo pi-estimation test.
- McPi
Result - Full result of a single Monte Carlo pi-estimation test run.
- Nist
Config - Configuration for the NIST SP 800-22 bit-level test suite.
- Nist
Item Result - Result of a single NIST sub-test.
- Nist
Result - Full result of one run of the NIST SP 800-22 bit-level suite.
- Paranoid
Config - Configuration for a paranoid (battery-level) meta-test.
- Paranoid
Result - Full result of a paranoid meta-test run.
- Runs
Config - Configuration for a runs test.
- Runs
Result - Full result of a single runs test run.
- Serial
Config - Configuration for a serial (multi-dimensional uniformity) test.
- Serial
Result - Full result of a single serial test run.
Enums§
- Birthday
Error - Errors that can occur while configuring or running a birthday spacing test.
- ChiSq
Error - Errors that can occur while configuring or running a chi-squared test.
- KsError
- Errors that can occur while configuring or running a KS test.
- McPi
Error - Errors that can occur while configuring or running a Monte Carlo pi test.
- Nist
Error - Errors that can occur while configuring or running the NIST suite.
- Nist
Test - The individual NIST SP 800-22 sub-tests implemented by this module.
- Paranoid
Error - Errors that can occur while configuring or running a paranoid meta-test.
- Runs
Error - Errors that can occur while configuring or running a runs test.
- Serial
Error - Errors that can occur while configuring or running a serial test.
- Verdict
Functions§
- erfc
- Complementary error function
erfc(x), using the Abramowitz & Stegun 7.1.26 rational approximation (max absolute error ~1.5e-7). Sufficient precision for p-value computation in statistical randomness tests. - igamc
- Upper regularized incomplete gamma function
Q(a, x) = Gamma(a, x) / Gamma(a), used to derive chi-squared p-values:p = igamc(df / 2, chi2 / 2). - normal_
cdf - Standard normal CDF
Phi(x), derived fromerfc. - p_
value_ from_ z - Converts a two-sided z-score (as produced by
crate::chisq::ChiSqResult,crate::serial::SerialResult, orcrate::runs::RunsResult) into an approximate p-value, suitable as a trial input torun_paranoid. - run_
birthday - Runs a birthday spacing test for the given named 64-bit-word sampler and configuration.
- run_
chisq - Runs a chi-squared uniformity test for the given named sampler and configuration.
- run_ks
- Runs a one-sample Kolmogorov-Smirnov uniformity test for the given named sampler and configuration.
- run_
mcpi - Runs a Monte Carlo pi-estimation test for the given named sampler and configuration.
- run_
nist - Runs the NIST SP 800-22 bit-level test suite for the given named
word-sampler and configuration.
word_bitsis the number of low bits of eachword_sampler()draw that carry entropy (typically 32 or 64). - run_
paranoid - Runs a paranoid (battery-level) meta-test: calls
trialonce per configured trial count, wheretrial(i)must return the p-value of thei-th independent run of the underlying test, then applies the NIST SP 800-22 4.2 proportion-of-passes and p-value-uniformity checks. - run_
runs - Runs a runs test (Wald-Wolfowitz-style, up/down variant) for the given named sampler and configuration.
- run_
serial - Runs a serial uniformity test for the given named sampler and configuration.
- unit_
f64_ from_ u32 - Converts a raw 32-bit word into a uniform
[0, 1)float. - unit_
f64_ from_ u64 - Converts a raw 64-bit word into a uniform
[0, 1)float, using the top 53 bits.