added observation and z-score to results, emojis

This commit is contained in:
randogoth 2025-01-17 13:27:34 +02:00
parent 10ff2123ac
commit a8c74caf90
19 changed files with 121 additions and 98 deletions

1
Cargo.lock generated
View file

@ -189,7 +189,6 @@ version = "0.1.0"
dependencies = [
"flate2",
"kolmogorov_smirnov",
"rand_distr",
"statrs",
]

View file

@ -6,5 +6,4 @@ edition = "2021"
[dependencies]
flate2 = "1.0.35"
kolmogorov_smirnov = "1.1.0"
rand_distr = "0.4.3"
statrs = "0.18.0"

View file

@ -1,7 +1,6 @@
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use statrs::distribution::{Normal, ContinuousCDF};
use onod3000::Onod;
@ -36,12 +35,16 @@ fn main() -> io::Result<()> {
std::process::exit(1);
}
println!("\nTesting {}", source);
println!("Testing {} bytes.", input_data.len());
println!("--------------------------------------");
println!("\nTesting {} bytes from {}.", input_data.len(), source);
println!("-----------------------------------------------------");
println!("Randomness Test Observation Z-Score P-Value Pass");
println!("-----------------------------------------------------");
let alpha = 0.01;
let mut passed_tests = 0;
let alpha = 0.01;
// Create vectors to store results
let (mut observations, mut z_scores, mut p_values) = (Vec::new(), Vec::new(), Vec::new());
// Run each test with the appropriate handling
let tests = [
@ -52,7 +55,7 @@ fn main() -> io::Result<()> {
("ChiByte", Onod::chi_byte(&input_data)),
("MeanByte", Onod::mean_byte(&input_data)),
("Compression", Onod::compression(&input_data)),
("KS", Onod::ks(&input_data)),
("Kolm.-Smirnov", Onod::ks(&input_data)),
("Pi", Onod::pi(&input_data)),
("Shells", Onod::shells(&input_data)),
("Gaps", Onod::gaps(&input_data)),
@ -63,47 +66,31 @@ fn main() -> io::Result<()> {
("UnCorrelation", Onod::un_correlation(&input_data)),
];
let mut p_values = Vec::new();
for (test_name, p_value) in tests.iter() {
let result = if *p_value >= alpha {
for (test_name, (observation, z_score, p_value)) in &tests {
let result = if *p_value >= alpha && *observation != -1.0 {
passed_tests += 1;
"PASS"
""
} else {
"FAIL"
if *observation == -1.0 {
"SKIP"
} else {
""
}
};
println!("{:<20} p = {:.4}, {}", test_name, p_value, result);
p_values.push(*p_value); // Dereference the value and push it
}
// Calculate combined p-value using Fisher's method
let (combined_z_score, combined_p_value) = combined_score_stouffer(&p_values);
let overall_result = if combined_p_value >= alpha { "PASS" } else { "FAIL" };
println!(
"{:<15} {:>15.3} {:>8.4} {:.4} {}",
test_name, observation, z_score, p_value, result
);
println!("--------------------------------------");
observations.push(*observation);
z_scores.push(*z_score);
p_values.push(*p_value);
}
println!("-----------------------------------------------------");
println!("{}/{} tests passed.", passed_tests, tests.len());
println!("--------------------------------------");
println!("Combined Z-Score = {:.6}\nCombined P-Value = {:.6}\nOverall Result: {}", combined_z_score.abs(), combined_p_value, overall_result);
println!("-----------------------------------------------------");
Ok(())
}
pub fn combined_score_stouffer(p_values: &[f64]) -> (f64, f64) {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
// Calculate Z-scores for each p-value
let z_scores: Vec<f64> = p_values
.iter()
.map(|&p| normal_dist.inverse_cdf(1.0 - p))
.collect();
// Combine Z-scores
let combined_z = z_scores.iter().sum::<f64>() / (p_values.len() as f64).sqrt();
// Convert combined Z-score back to a p-value
let p_value = 2.0 * (1.0 - normal_dist.cdf(combined_z.abs())); // Two-tailed
(combined_z, p_value)
}

View file

@ -6,12 +6,12 @@ impl Onod {
/// Avalanche randomness test
/// Compares the bit-level differences between consecutive chunks of data and returns a p-value.
pub fn avalanche(samples: &[u8]) -> f64 {
pub fn avalanche(samples: &[u8]) -> (f64, f64, f64) {
const XOR_WINDOW_SIZE: usize = 20; // Bytes. Equivalent to SHA-1 (160 bits).
if samples.len() < 2 * XOR_WINDOW_SIZE {
return 0.0; // Not enough data for meaningful calculation
return (-1.0, 0.0, 1.0); // Not enough data for meaningful calculation
}
let mut means = Vec::new();
@ -46,7 +46,7 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
p_value
(mean_observed, z_score, p_value)
}
}

View file

@ -4,10 +4,10 @@ use crate::Onod;
impl Onod {
pub fn chi_bit(samples: &[u8]) -> f64 {
pub fn chi_bit(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // empty data
return (-1.0, 0.0, 1.0); // empty data
}
// Lookup table for the number of set bits in each byte (Hamming weight)
@ -57,6 +57,12 @@ impl Onod {
let chi_squared_dist = ChiSquared::new(degrees_of_freedom).expect("Failed to create ChiSquared distribution");
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
// Z-score calculation (standardization of the chi-squared statistic)
let mean = degrees_of_freedom; // Mean of the chi-squared distribution
let std_dev = (2.0 * degrees_of_freedom).sqrt(); // Standard deviation of the chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
(chi_squared_stat, z_score, p_value)
}
}

View file

@ -7,10 +7,10 @@ impl Onod {
/// ChiByte randomness test
/// Evaluates the uniformity of byte values across the data and returns a p-value.
pub fn chi_byte(samples: &[u8]) -> f64 {
pub fn chi_byte(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // Default to perfect randomness for empty data
return (-1.0, 0.0, 1.0); // Default to perfect randomness for empty data
}
// Count occurrences of each byte value (0-255)
@ -35,7 +35,12 @@ impl Onod {
let chi_squared_dist = ChiSquared::new(degrees_of_freedom).expect("Failed to create ChiSquared distribution");
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
// Z-score calculation (standardization of the chi-squared statistic)
let mean = degrees_of_freedom; // Mean of the chi-squared distribution
let std_dev = (2.0 * degrees_of_freedom).sqrt(); // Standard deviation of the chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
(chi_squared_stat, z_score, p_value)
}
}

View file

@ -8,19 +8,19 @@ impl Onod {
/// Compression randomness test
/// Estimates randomness by the compressibility of the data and returns a p-value.
pub fn compression(samples: &[u8]) -> f64 {
pub fn compression(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // Perfect randomness for empty data
return (-1.0, 0.0, 1.0); // Perfect randomness for empty data
}
// Compress the data using deflate
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
if encoder.write_all(samples).is_err() {
return 0.0; // Compression failed
return (-1.0, 0.0, 1.0); // Compression failed
}
let compressed_data = match encoder.finish() {
Ok(data) => data,
Err(_) => return 0.0, // Compression failed
Err(_) => return (-1.0, 0.0, 1.0), // Compression failed
};
// Calculate compression ratio
@ -37,6 +37,6 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs())); // Two-tailed test
p_value
(compression_ratio, z_score, p_value)
}
}

View file

@ -6,10 +6,10 @@ impl Onod {
/// Gaps randomness test
/// Analyzes the gaps between occurrences of a specific byte value and returns a p-value.
pub fn gaps(samples: &[u8]) -> f64 {
pub fn gaps(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // empty data
return (-1.0, 0.0, 1.0); // empty data
}
// Bin edges and expected frequencies from the Java implementation
@ -41,7 +41,7 @@ impl Onod {
}
if all_gaps.is_empty() {
return 1.0; // No gaps found, assume randomness
return (-1.0, 0.0, 1.0); // No gaps found
}
// Create histogram of observed gaps
@ -73,7 +73,12 @@ impl Onod {
let chi_squared_dist = ChiSquared::new(degrees_of_freedom).expect("Failed to create ChiSquared distribution");
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
// Z-score calculation (standardization of the chi-squared statistic)
let mean = degrees_of_freedom; // Mean of the chi-squared distribution
let std_dev = (2.0 * degrees_of_freedom).sqrt(); // Standard deviation of the chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
(chi_squared_stat, z_score, p_value)
}
}

View file

@ -14,10 +14,10 @@ impl Onod {
// `kolmogorov_smirnov` crate, does not add jitter or handle ties in the same way, and
// adheres to the crate's internal handling of floating-point comparisons. These differences
// may result in slight variations in p-values or KS statistics between the two versions.
pub fn ks(samples: &[u8]) -> f64 {
pub fn ks(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // empty data
return (-1.0, 0.0, 1.0); // empty data
}
// Normalize the input samples to [0, 1] range
@ -32,9 +32,16 @@ impl Onod {
let confidence = 0.01; // Significance level
let result = test_f64(&normalized_samples, &uniform_distribution, confidence);
// Extract p-value from the result
// Extract the KS statistic (D-statistic)
let ks_statistic = result.statistic;
// Calculate the z-score
let sample_size = normalized_samples.len() as f64;
let z_score = ks_statistic * sample_size.sqrt();
// Extract the p-value
let p_value = 1.0 - result.reject_probability;
p_value
(ks_statistic, z_score, p_value)
}
}

View file

@ -6,11 +6,11 @@ impl Onod {
/// Mean randomness test
/// Calculates the p-value for the mean of the byte slice compared to expected mean.
pub fn mean_byte(samples: &[u8]) -> f64 {
pub fn mean_byte(samples: &[u8]) -> (f64, f64, f64) {
let len = samples.len() as f64;
if len == 0.0 {
return 0.0;
return (-1.0, 0.0, 1.0);
}
// Calculate observed mean
@ -29,6 +29,6 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs())); // Two-tailed test
p_value
(observed_mean, z_score, p_value)
}
}

View file

@ -6,10 +6,10 @@ impl Onod {
/// Monobit randomness test
/// Evaluates the balance of 0s and 1s in the binary representation of the data and returns a p-value.
pub fn monobit(samples: &[u8]) -> f64 {
pub fn monobit(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // Default to perfect randomness for empty data
return (-1.0, 0.0, 1.0); // Default to perfect randomness for empty data
}
// Count the total number of 1s in the dataset
@ -35,6 +35,6 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
p_value
(observed_proportion, z_score, p_value)
}
}

View file

@ -24,10 +24,10 @@ impl Onod {
///
/// These differences are generally negligible for practical purposes and do not affect the overall functionality or
/// statistical significance of the test.
pub fn pi(samples: &[u8]) -> f64 {
pub fn pi(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0;
return (-1.0, 0.0, 1.0);
}
// Normalize samples to [0.0, 1.0)
@ -61,7 +61,7 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
p_value
(test_statistic, z_score, p_value)
}
}

View file

@ -6,10 +6,10 @@ impl Onod {
/// Prediction randomness test
/// Evaluates the predictability of the next bit based on current data and returns a p-value.
pub fn prediction(samples: &[u8]) -> f64 {
pub fn prediction(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0;
return (-1.0, 0.0, 1.0);
}
let mut correct_predictions = 0;
@ -29,7 +29,7 @@ impl Onod {
}
if total_predictions == 0 {
return 0.0; // No predictions possible
return (-1.0, 0.0, 1.0); // No predictions possible
}
// Calculate observed proportion of correct predictions
@ -46,6 +46,6 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
p_value
(observed_proportion, z_score, p_value)
}
}

View file

@ -6,10 +6,10 @@ impl Onod {
/// Runs randomness test
/// Evaluates the randomness by counting both increasing and decreasing runs and returns a p-value.
pub fn runs(samples: &[u8]) -> f64 {
pub fn runs(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0;
return (-1.0, 0.0, 1.0);
}
// Clone the samples to avoid modifying the original input
@ -53,10 +53,10 @@ impl Onod {
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
if p_value.is_nan() {
return 0.0; // Return 0 if p-value is NaN
return (-1.0, 0.0, 1.0); // Return 0 if p-value is NaN
}
p_value
(runs_observed as f64, z_score, p_value)
}
}

View file

@ -6,12 +6,12 @@ impl Onod {
/// RunUps randomness test
/// Evaluates the number of four-byte run-ups in the data and returns a p-value.
pub fn run_ups(input: &[u8]) -> f64 {
pub fn run_ups(input: &[u8]) -> (f64, f64, f64) {
let samples = input.iter().map(|&x| x as u32).collect::<Vec<u32>>();
if samples.len() < 4 {
return 0.0; // Not enough data for meaningful calculation
return (-1.0, 0.0, 1.0); // Not enough data for meaningful calculation
}
let mut test_statistic = 0;
@ -39,6 +39,11 @@ impl Onod {
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
// Z-score calculation (standardization of the chi-squared statistic)
let mean = 1.0; // Mean of the chi-squared distribution
let std_dev = (2.0 as f64).sqrt(); // Standard deviation of the chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
(chi_squared_stat, z_score, p_value)
}
}

View file

@ -4,9 +4,9 @@ impl Onod {
/// Sanity randomness test
/// Checks for basic properties of randomness and returns a p-value.
pub fn sanity(samples: &[u8]) -> f64 {
pub fn sanity(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0;
return (-1.0, 0.0, 1.0);
}
// Check the proportion of ones and zeros in the byte data
@ -22,12 +22,15 @@ impl Onod {
let expected_ratio = 0.5; // Expected for a truly random sequence
let std_dev = (0.5 * 0.5 / total_bits as f64).sqrt(); // Standard deviation for binomial distribution
// Z-score calculation
let z_score = (observed_ratio - expected_ratio) / std_dev;
// Use normal distribution to calculate p-value
use statrs::distribution::{Normal, ContinuousCDF};
let normal_dist = Normal::new(expected_ratio, std_dev).expect("Failed to create Normal distribution");
let p_value = 1.0 - normal_dist.cdf(observed_ratio);
p_value
(observed_ratio, z_score, p_value)
}
}

View file

@ -5,11 +5,11 @@ use crate::Onod;
impl Onod {
/// Entropy randomness test
/// Calculates the Shannon entropy of a byte slice and outputs a p-value.
pub fn shannon(samples: &[u8]) -> f64 {
pub fn shannon(samples: &[u8]) -> (f64, f64, f64) {
let len = samples.len() as f64;
if len == 0.0 {
return 0.0;
return (-1.0, 0.0, 1.0);
}
// Count occurrences of each byte
@ -38,6 +38,6 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs())); // Two-tailed test
p_value
(entropy, z_score, p_value)
}
}

View file

@ -6,7 +6,7 @@ impl Onod {
/// Shells randomness test
/// Evaluates the uniformity of distances between identical byte values and returns a p-value.
pub fn shells(input: &[u8]) -> f64 {
pub fn shells(input: &[u8]) -> (f64, f64, f64) {
// Define shell radii (precomputed to ensure equal volumes)
const SHELL_RADII: [f64; 35] = [
@ -24,8 +24,10 @@ impl Onod {
let samples = convert_to_3d_points(input);
if samples.len() < 25000 {
eprintln!("Shells test requires at least 25,000 points for statistical validity.");
return 0.0; // Skip the test for small datasets
// eprintln!("---------------------------------------------------------------");
// eprintln!("ERROR: Shells test requires at least 25,000 points for statistical validity. Skipping.");
// eprintln!("---------------------------------------------------------------");
return (-1.0, 0.0, 1.0); // Skip the test for small datasets
}
let sphere_radius = SHELL_RADII[0];
@ -75,7 +77,12 @@ impl Onod {
let chi_squared_dist = ChiSquared::new(degrees_of_freedom).expect("Failed to create ChiSquared distribution");
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
// Z-score calculation (standardization of the chi-squared statistic)
let mean = degrees_of_freedom; // Mean of the chi-squared distribution
let std_dev = (2.0 * degrees_of_freedom).sqrt(); // Standard deviation of the chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
(chi_squared_stat, z_score, p_value)
}
}

View file

@ -6,12 +6,12 @@ impl Onod {
/// UnCorrelation randomness test
/// Computes the Pearson correlation between the sequence and its shifted version, returning a p-value.
pub fn un_correlation(input: &[u8]) -> f64 {
pub fn un_correlation(input: &[u8]) -> (f64, f64, f64) {
let samples = input.iter().map(|&x| x as i32).collect::<Vec<i32>>();
if samples.len() < 2 {
return 0.0; // Default to perfect randomness for insufficient data
return (-1.0, 0.0, 1.0); // Default to perfect randomness for insufficient data
}
// Convert samples to f64 for correlation computation
@ -51,7 +51,7 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(t_stat.abs()));
p_value
(correlation, t_stat, p_value)
}
}