generated from ProcyOS/rust-template
add runs of ones test #8
5 changed files with 110 additions and 4 deletions
|
@ -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 simple monobit test.
|
||||||
- Added a block frequency test.
|
- Added a block frequency test.
|
||||||
- Added a runs test.
|
- Added a runs test.
|
||||||
|
- Added a longest runs of ones test.
|
||||||
|
|
||||||
[Unreleased]: https://git.chir.rs/ProcyOS/rand_testsuite
|
[Unreleased]: https://git.chir.rs/ProcyOS/rand_testsuite
|
||||||
|
|
|
@ -11,7 +11,7 @@ categories = ["development-tools::testing", "no-std", "no-std::no-alloc"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitvec = { version = "1.0.1", default-features = false }
|
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"
|
libm = "0.2.8"
|
||||||
num-traits = { version = "0.2.19", default-features = false, features = [
|
num-traits = { version = "0.2.19", default-features = false, features = [
|
||||||
"libm",
|
"libm",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! All supported tests are available in this module.
|
//! All supported tests are available in this module.
|
||||||
|
|
||||||
mod block_frequency;
|
mod block_frequency;
|
||||||
|
mod longest_runs_of_ones;
|
||||||
mod monobit;
|
mod monobit;
|
||||||
mod runs;
|
mod runs;
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ 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 longest_runs_of_ones::*;
|
||||||
pub use monobit::*;
|
pub use monobit::*;
|
||||||
pub use runs::*;
|
pub use runs::*;
|
||||||
|
|
||||||
|
@ -34,12 +36,14 @@ impl RandomTest for CombinedRandomTest {
|
||||||
Monobit::RECOMMENDED_BIT_SIZE,
|
Monobit::RECOMMENDED_BIT_SIZE,
|
||||||
BlockFrequency::RECOMMENDED_BIT_SIZE,
|
BlockFrequency::RECOMMENDED_BIT_SIZE,
|
||||||
Runs::RECOMMENDED_BIT_SIZE,
|
Runs::RECOMMENDED_BIT_SIZE,
|
||||||
|
LongestRunOfOnes::RECOMMENDED_BIT_SIZE,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const MINIMUM_BIT_SIZE: usize = max_of([
|
const MINIMUM_BIT_SIZE: usize = max_of([
|
||||||
Monobit::MINIMUM_BIT_SIZE,
|
Monobit::MINIMUM_BIT_SIZE,
|
||||||
BlockFrequency::MINIMUM_BIT_SIZE,
|
BlockFrequency::MINIMUM_BIT_SIZE,
|
||||||
Runs::MINIMUM_BIT_SIZE,
|
Runs::MINIMUM_BIT_SIZE,
|
||||||
|
LongestRunOfOnes::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> {
|
||||||
|
@ -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(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)?);
|
worst_test_score = worst_test_score.min(Runs.evaluate(bs)?);
|
||||||
|
worst_test_score = worst_test_score.min(LongestRunOfOnes.evaluate(bs)?);
|
||||||
Some(worst_test_score)
|
Some(worst_test_score)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
102
src/tests/longest_runs_of_ones.rs
Normal file
102
src/tests/longest_runs_of_ones.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
//! Various utility functions
|
//! Various utility functions
|
||||||
|
|
||||||
use core::convert::identity;
|
|
||||||
|
|
||||||
use bitvec::{order::BitOrder, slice::BitSlice, store::BitStore};
|
use bitvec::{order::BitOrder, slice::BitSlice, store::BitStore};
|
||||||
|
|
||||||
/// Calculates the proportion of ones in a bitslice
|
/// 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
|
bs.into_iter().filter(|b| **b).count() as f64 / bs.len() as f64
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue