add runs of ones test #8

Open
darkkirb wants to merge 2 commits from add-runs-of-ones into main
5 changed files with 110 additions and 4 deletions

View file

@ -11,5 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a simple monobit test.
- Added a block frequency test.
- Added a runs test.
- Added a longest runs of ones test.
[Unreleased]: https://git.chir.rs/ProcyOS/rand_testsuite

View file

@ -11,7 +11,7 @@ categories = ["development-tools::testing", "no-std", "no-std::no-alloc"]
[dependencies]
bitvec = { version = "1.0.1", default-features = false }
extra-math = { version = "0.1.0", registry = "procyos" }
extra-math = { version = "0.1.1", registry = "procyos" }
libm = "0.2.8"
num-traits = { version = "0.2.19", default-features = false, features = [
"libm",

View file

@ -1,6 +1,7 @@
//! All supported tests are available in this module.
mod block_frequency;
mod longest_runs_of_ones;
mod monobit;
mod runs;
@ -8,6 +9,7 @@ use core::f64;
use bitvec::prelude::{BitOrder, BitSlice, BitStore};
pub use block_frequency::*;
pub use longest_runs_of_ones::*;
pub use monobit::*;
pub use runs::*;
@ -34,12 +36,14 @@ impl RandomTest for CombinedRandomTest {
Monobit::RECOMMENDED_BIT_SIZE,
BlockFrequency::RECOMMENDED_BIT_SIZE,
Runs::RECOMMENDED_BIT_SIZE,
LongestRunOfOnes::RECOMMENDED_BIT_SIZE,
]);
const MINIMUM_BIT_SIZE: usize = max_of([
Monobit::MINIMUM_BIT_SIZE,
BlockFrequency::MINIMUM_BIT_SIZE,
Runs::MINIMUM_BIT_SIZE,
LongestRunOfOnes::MINIMUM_BIT_SIZE,
]);
fn evaluate<T: BitStore, O: BitOrder>(&self, bs: &BitSlice<T, O>) -> Option<f64> {
@ -47,6 +51,7 @@ impl RandomTest for CombinedRandomTest {
worst_test_score = worst_test_score.min(Monobit.evaluate(bs)?);
worst_test_score = worst_test_score.min(BlockFrequency.evaluate(bs)?);
worst_test_score = worst_test_score.min(Runs.evaluate(bs)?);
worst_test_score = worst_test_score.min(LongestRunOfOnes.evaluate(bs)?);
Some(worst_test_score)
}
}

View file

@ -0,0 +1,102 @@
//! Longest runs of ones test
use bitvec::prelude::{BitOrder, BitSlice, BitStore};
use extra_math::gamma::Gamma;
use crate::RandomTest;
const EXPECTED_M8: [f64; 4] = [0.2148, 0.3672, 0.2305, 0.1875];
const EXPECTED_M128: [f64; 6] = [0.1174, 0.2430, 0.2493, 0.1752, 0.1027, 0.1124];
const EXPECTED_M10K: [f64; 7] = [0.0882, 0.2092, 0.2483, 0.1933, 0.1208, 0.0675, 0.0727];
/// Longest runs of ones test
///
/// This test is defined in the [NIST Special Publication 800-22](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-22r1a.pdf).
#[derive(Copy, Clone, Debug)]
pub struct LongestRunOfOnes;
impl RandomTest for LongestRunOfOnes {
const RECOMMENDED_BIT_SIZE: usize = 750_000;
const MINIMUM_BIT_SIZE: usize = 128;
fn evaluate<T: BitStore, O: BitOrder>(&self, bs: &BitSlice<T, O>) -> Option<f64> {
let block_size = match bs.len() {
0..128 => {
return None;
}
128..6272 => 8,
6272..750_000 => 128,
750_000.. => 10_000,
};
let mut occurences = [0usize; 7];
for block in bs.chunks(block_size) {
let mut c = 0usize;
for b in block.into_iter().map(|b| *b).chain([false]) {
if b {
c += 1;
} else if c > 0 {
match (block_size, c) {
(0..=8, 0..=1) | (9..=128, 0..=4) | (129.., 0..=10) => occurences[0] += 1,
(0..=8, 2) | (9..=128, 5) | (129.., 11) => occurences[1] += 1,
(0..=8, 3) | (9..=128, 6) | (129.., 12) => occurences[2] += 1,
(0..=8, 4..) | (9..=128, 7) | (129.., 13) => occurences[3] += 1,
(9..=128, 8) | (129.., 14) => occurences[4] += 1,
(9..=128, 9..) | (129.., 15) => occurences[5] += 1,
(129.., 16..) => occurences[6] += 1,
}
c = 0;
}
}
}
let mut chi_squared = 0.0;
let k = if block_size == 8 {
for i in 0..EXPECTED_M8.len() {
chi_squared +=
(occurences[i] as f64 - 16.0 * EXPECTED_M8[i]) / (16.0 * EXPECTED_M8[i]);
}
EXPECTED_M8.len()
} else if block_size == 128 {
for i in 0..EXPECTED_M128.len() {
chi_squared +=
(occurences[i] as f64 - 49.0 * EXPECTED_M128[i]) / (49.0 * EXPECTED_M128[i]);
}
EXPECTED_M128.len()
} else if block_size == 10_000 {
for i in 0..EXPECTED_M10K.len() {
chi_squared +=
(occurences[i] as f64 - 75.0 * EXPECTED_M10K[i]) / (75.0 * EXPECTED_M10K[i]);
}
EXPECTED_M10K.len()
} else {
unreachable!();
};
Some(Gamma::upper_gamma_regularized(
k as f64 / 2.0,
chi_squared / 2.0,
))
}
}
#[cfg(test)]
mod tests {
use bitvec::prelude::*;
use crate::RandomTest;
use super::LongestRunOfOnes;
#[test]
fn longest_run_of_ones_test_vector() {
let epsilon = bitvec![
1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1,
1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1,
1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0,
];
let res = LongestRunOfOnes.evaluate(epsilon.as_bitslice());
assert!(res.is_some());
let res = res.unwrap();
assert!(res > 0.01, "Data should be evaluated as random");
}
}

View file

@ -1,10 +1,8 @@
//! Various utility functions
use core::convert::identity;
use bitvec::{order::BitOrder, slice::BitSlice, store::BitStore};
/// Calculates the proportion of ones in a bitslice
pub fn ones_proportion<B: BitStore, O: BitOrder>(bs: &BitSlice<B, O>) -> f64 {
pub(crate) fn ones_proportion<B: BitStore, O: BitOrder>(bs: &BitSlice<B, O>) -> f64 {
bs.into_iter().filter(|b| **b).count() as f64 / bs.len() as f64
}