generated from ProcyOS/rust-template
add runs test
This commit is contained in:
parent
f9aa7594bd
commit
80b07fcfcf
6 changed files with 90 additions and 8 deletions
|
@ -10,5 +10,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Added
|
### Added
|
||||||
- Added a simple monobit test.
|
- Added a simple monobit test.
|
||||||
- Added a block frequency test.
|
- Added a block frequency test.
|
||||||
|
- Added a runs test.
|
||||||
|
|
||||||
[Unreleased]: https://git.chir.rs/ProcyOS/rand_testsuite
|
[Unreleased]: https://git.chir.rs/ProcyOS/rand_testsuite
|
||||||
|
|
|
@ -7,6 +7,7 @@ use bitvec::{order::BitOrder, slice::BitSlice, store::BitStore};
|
||||||
mod rand_core;
|
mod rand_core;
|
||||||
|
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
pub(crate) mod utils;
|
||||||
|
|
||||||
#[cfg(feature = "rand_core")]
|
#[cfg(feature = "rand_core")]
|
||||||
pub use rand_core::RandomTestExt;
|
pub use rand_core::RandomTestExt;
|
||||||
|
|
11
src/tests.rs
11
src/tests.rs
|
@ -2,12 +2,14 @@
|
||||||
|
|
||||||
mod block_frequency;
|
mod block_frequency;
|
||||||
mod monobit;
|
mod monobit;
|
||||||
|
mod runs;
|
||||||
|
|
||||||
use core::f64;
|
use core::f64;
|
||||||
|
|
||||||
use bitvec::prelude::{BitOrder, BitSlice, BitStore};
|
use bitvec::prelude::{BitOrder, BitSlice, BitStore};
|
||||||
pub use block_frequency::*;
|
pub use block_frequency::*;
|
||||||
pub use monobit::*;
|
pub use monobit::*;
|
||||||
|
pub use runs::*;
|
||||||
|
|
||||||
use crate::RandomTest;
|
use crate::RandomTest;
|
||||||
|
|
||||||
|
@ -31,15 +33,20 @@ impl RandomTest for CombinedRandomTest {
|
||||||
const RECOMMENDED_BIT_SIZE: usize = max_of([
|
const RECOMMENDED_BIT_SIZE: usize = max_of([
|
||||||
Monobit::RECOMMENDED_BIT_SIZE,
|
Monobit::RECOMMENDED_BIT_SIZE,
|
||||||
BlockFrequency::RECOMMENDED_BIT_SIZE,
|
BlockFrequency::RECOMMENDED_BIT_SIZE,
|
||||||
|
Runs::RECOMMENDED_BIT_SIZE,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const MINIMUM_BIT_SIZE: usize =
|
const MINIMUM_BIT_SIZE: usize = max_of([
|
||||||
max_of([Monobit::MINIMUM_BIT_SIZE, BlockFrequency::MINIMUM_BIT_SIZE]);
|
Monobit::MINIMUM_BIT_SIZE,
|
||||||
|
BlockFrequency::MINIMUM_BIT_SIZE,
|
||||||
|
Runs::MINIMUM_BIT_SIZE,
|
||||||
|
]);
|
||||||
|
|
||||||
fn evaluate<T: BitStore, O: BitOrder>(&self, bs: &BitSlice<T, O>) -> Option<f64> {
|
fn evaluate<T: BitStore, O: BitOrder>(&self, bs: &BitSlice<T, O>) -> Option<f64> {
|
||||||
let mut worst_test_score = f64::MAX;
|
let mut worst_test_score = f64::MAX;
|
||||||
worst_test_score = worst_test_score.min(Monobit.evaluate(bs)?);
|
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(BlockFrequency.evaluate(bs)?);
|
||||||
|
worst_test_score = worst_test_score.min(Runs.evaluate(bs)?);
|
||||||
Some(worst_test_score)
|
Some(worst_test_score)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use extra_math::gamma::Gamma;
|
||||||
#[allow(unused_imports, reason = "redundant in std use cases")]
|
#[allow(unused_imports, reason = "redundant in std use cases")]
|
||||||
use num_traits::Float;
|
use num_traits::Float;
|
||||||
|
|
||||||
use crate::RandomTest;
|
use crate::{utils::ones_proportion, RandomTest};
|
||||||
|
|
||||||
/// The Block Frequency Test splits the input data into multiple blocks, counts the frequency 1s and 0s within these blocks, and determines whether these are distributed well.
|
/// The Block Frequency Test splits the input data into multiple blocks, counts the frequency 1s and 0s within these blocks, and determines whether these are distributed well.
|
||||||
///
|
///
|
||||||
|
@ -26,11 +26,7 @@ impl RandomTest for BlockFrequency {
|
||||||
let block_count = bs.len() / block_size;
|
let block_count = bs.len() / block_size;
|
||||||
let mut statistic = 0.0;
|
let mut statistic = 0.0;
|
||||||
for i in 0..block_count {
|
for i in 0..block_count {
|
||||||
let one_count: usize = bs[i * block_size..(i + 1) * block_size]
|
let pi = ones_proportion(&bs[i * block_size..(i + 1) * block_size]);
|
||||||
.into_iter()
|
|
||||||
.map(|v| if *v { 1 } else { 0 })
|
|
||||||
.sum();
|
|
||||||
let pi = (one_count as f64) / (block_size as f64);
|
|
||||||
statistic += (pi - 0.5).powi(2);
|
statistic += (pi - 0.5).powi(2);
|
||||||
}
|
}
|
||||||
statistic *= (4 * block_size) as f64;
|
statistic *= (4 * block_size) as f64;
|
||||||
|
|
67
src/tests/runs.rs
Normal file
67
src/tests/runs.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
//! Runs test code
|
||||||
|
|
||||||
|
use bitvec::{order::BitOrder, slice::BitSlice, store::BitStore};
|
||||||
|
use libm::erfc;
|
||||||
|
#[allow(unused_imports, reason = "redundant in std use cases")]
|
||||||
|
use num_traits::Float;
|
||||||
|
|
||||||
|
use crate::{utils::ones_proportion, RandomTest};
|
||||||
|
|
||||||
|
/// The runs test counts the number of bit transitions inside of a random number generator.
|
||||||
|
///
|
||||||
|
/// 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 Runs;
|
||||||
|
|
||||||
|
impl RandomTest for Runs {
|
||||||
|
const RECOMMENDED_BIT_SIZE: usize = 100;
|
||||||
|
const MINIMUM_BIT_SIZE: usize = 2;
|
||||||
|
|
||||||
|
fn evaluate<T: BitStore, O: BitOrder>(&self, bs: &BitSlice<T, O>) -> Option<f64> {
|
||||||
|
if bs.len() < Self::MINIMUM_BIT_SIZE {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let pi = ones_proportion(bs);
|
||||||
|
if (pi - 0.5).abs() > 2.0 / (bs.len() as f64).sqrt() {
|
||||||
|
return Some(0.0);
|
||||||
|
}
|
||||||
|
let (_, runs) = bs
|
||||||
|
.into_iter()
|
||||||
|
.skip(1)
|
||||||
|
.fold((bs[0], 1usize), |(last, cum), cur| {
|
||||||
|
if *cur == last {
|
||||||
|
(last, cum)
|
||||||
|
} else {
|
||||||
|
(*cur, cum + 1)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(erfc(
|
||||||
|
(runs as f64 - 2.0 * (bs.len() as f64) * pi * (1.0 - pi)).abs()
|
||||||
|
/ (2.0 * (2.0 * bs.len() as f64).sqrt() * pi * (1.0 - pi)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use bitvec::prelude::*;
|
||||||
|
|
||||||
|
use crate::RandomTest;
|
||||||
|
|
||||||
|
use super::Runs;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn runs_test_vector() {
|
||||||
|
let epsilon = bitvec![
|
||||||
|
1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0,
|
||||||
|
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||||
|
1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1,
|
||||||
|
0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0
|
||||||
|
];
|
||||||
|
let res = Runs.evaluate(epsilon.as_bitslice());
|
||||||
|
assert!(res.is_some());
|
||||||
|
let res = res.unwrap();
|
||||||
|
assert!(res > 0.01, "Data should be evaluated as random");
|
||||||
|
}
|
||||||
|
}
|
10
src/utils.rs
Normal file
10
src/utils.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
//! 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 {
|
||||||
|
bs.into_iter().filter(|b| **b).count() as f64 / bs.len() as f64
|
||||||
|
}
|
Loading…
Reference in a new issue