pub fn prd(orig: &[f64], recon: &[f64]) -> f64 {
const EPS: f64 = 1e-12;
let n = orig.len().min(recon.len());
let mut num = 0.0f64;
let mut den = 0.0f64;
for i in 0..n {
let d = orig[i] - recon[i];
num += d * d;
den += orig[i] * orig[i];
}
if den < EPS {
return if num < EPS { 0.0 } else { 100.0 };
}
100.0 * (num / den).sqrt()
}
pub fn prd_is_exact_zero(orig: &[i64], recon: &[i64]) -> bool {
orig.len() == recon.len() && orig == recon
}
pub fn prdn(orig: &[f64], recon: &[f64]) -> f64 {
const EPS: f64 = 1e-12;
let n = orig.len().min(recon.len());
if n == 0 {
return 0.0;
}
let mean = orig[..n].iter().sum::<f64>() / n as f64;
let mut num = 0.0f64;
let mut den = 0.0f64;
for i in 0..n {
let d = orig[i] - recon[i];
num += d * d;
let c = orig[i] - mean;
den += c * c;
}
if den < EPS {
return if num < EPS { 0.0 } else { 100.0 };
}
100.0 * (num / den).sqrt()
}
pub fn pearson_r(orig: &[f64], recon: &[f64]) -> f64 {
const STD_EPS: f64 = 1e-8;
let n = orig.len().min(recon.len());
if n == 0 {
return 0.0;
}
let nf = n as f64;
let mx = orig[..n].iter().sum::<f64>() / nf;
let my = recon[..n].iter().sum::<f64>() / nf;
let mut sxx = 0.0f64;
let mut syy = 0.0f64;
let mut sxy = 0.0f64;
for i in 0..n {
let dx = orig[i] - mx;
let dy = recon[i] - my;
sxx += dx * dx;
syy += dy * dy;
sxy += dx * dy;
}
let std_x = (sxx / nf).sqrt();
let std_y = (syy / nf).sqrt();
if std_x < STD_EPS || std_y < STD_EPS {
let equal = orig[..n]
.iter()
.zip(&recon[..n])
.all(|(a, b)| (a - b).abs() <= 1e-8);
return if equal { 1.0 } else { 0.0 };
}
let den = (sxx * syy).sqrt();
if den == 0.0 {
return 0.0;
}
sxy / den
}
pub fn snr_db(orig: &[f64], recon: &[f64]) -> f64 {
const NOISE_EPS: f64 = 1e-30;
let n = orig.len().min(recon.len());
if n == 0 {
return 120.0;
}
let nf = n as f64;
let mut sig = 0.0f64;
let mut noise = 0.0f64;
for i in 0..n {
sig += orig[i] * orig[i];
let d = orig[i] - recon[i];
noise += d * d;
}
let sig = sig / nf;
let noise = noise / nf;
if noise < NOISE_EPS {
return 120.0;
}
10.0 * (sig / noise).log10()
}
pub fn compression_ratio(raw_bytes: u64, comp_bytes: u64) -> f64 {
raw_bytes as f64 / comp_bytes.max(1) as f64
}
pub fn aggregate_cr(pairs: &[(u64, u64)]) -> f64 {
let mut raw = 0u64;
let mut comp = 0u64;
for (r, c) in pairs {
raw += *r;
comp += *c;
}
raw as f64 / comp.max(1) as f64
}
pub fn qs(cr: f64, prd: f64) -> f64 {
if prd <= 0.0 {
cr
} else {
cr / prd
}
}
pub fn entropy_from_counts(counts: &[u64]) -> f64 {
let total: u64 = counts.iter().sum();
if total == 0 {
return 0.0;
}
let total_f = total as f64;
let mut h = 0.0f64;
for &c in counts {
if c == 0 {
continue;
}
let p = c as f64 / total_f;
h -= p * p.log2();
}
h
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prd_of_identical_is_zero() {
let x = vec![1.0, 2.0, 3.0, 4.0, -5.0];
assert_eq!(prd(&x, &x), 0.0);
}
#[test]
fn prd_all_zero_guard() {
let z = vec![0.0, 0.0, 0.0];
assert_eq!(prd(&z, &z), 0.0);
assert_eq!(prd(&z, &[0.0, 1.0, 0.0]), 100.0);
}
#[test]
fn prd_known_vector() {
let x = vec![3.0, 4.0];
let xhat = vec![0.0, 0.0];
assert!((prd(&x, &xhat) - 100.0).abs() < 1e-9);
let a = vec![10.0, 0.0];
let b = vec![9.0, 0.0];
assert!((prd(&a, &b) - 10.0).abs() < 1e-9);
}
#[test]
fn exact_zero_integer_domain() {
let x = vec![1i64, -2, 3, 1000, -32768];
let y = x.clone();
assert!(prd_is_exact_zero(&x, &y));
let mut z = x.clone();
z[2] += 1; assert!(!prd_is_exact_zero(&x, &z));
assert!(!prd_is_exact_zero(&x, &x[..4]));
}
#[test]
fn pearson_of_identical_is_one() {
let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!((pearson_r(&x, &x) - 1.0).abs() < 1e-12);
}
#[test]
fn pearson_perfect_anticorrelation() {
let x = vec![1.0, 2.0, 3.0, 4.0];
let y = vec![4.0, 3.0, 2.0, 1.0];
assert!((pearson_r(&x, &y) + 1.0).abs() < 1e-9);
}
#[test]
fn pearson_known_linear() {
let x = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let y: Vec<f64> = x.iter().map(|v| 2.0 * v + 1.0).collect();
assert!((pearson_r(&x, &y) - 1.0).abs() < 1e-9);
}
#[test]
fn pearson_flat_guard() {
let a = vec![5.0, 5.0, 5.0];
assert_eq!(pearson_r(&a, &a), 1.0);
let b = vec![7.0, 7.0, 7.0];
assert_eq!(pearson_r(&a, &b), 0.0);
}
#[test]
fn prdn_known_vector() {
let x = vec![0.0, 2.0];
let r = vec![0.0, 0.0];
let expected = 100.0 * (4.0f64 / 2.0).sqrt();
assert!((prdn(&x, &r) - expected).abs() < 1e-9);
}
#[test]
fn snr_identical_capped() {
let x = vec![1.0, 2.0, 3.0];
assert_eq!(snr_db(&x, &x), 120.0);
}
#[test]
fn snr_known() {
let x = vec![10.0];
let y = vec![9.0];
assert!((snr_db(&x, &y) - 20.0).abs() < 1e-9);
}
#[test]
fn cr_basics() {
assert_eq!(compression_ratio(1000, 100), 10.0);
assert_eq!(compression_ratio(50, 0), 50.0);
}
#[test]
fn aggregate_cr_pools_bytes() {
let pairs = vec![(1000u64, 100u64), (1000u64, 900u64)];
assert!((aggregate_cr(&pairs) - 2.0).abs() < 1e-12);
assert_eq!(aggregate_cr(&[]), 0.0);
}
#[test]
fn qs_guarded() {
assert_eq!(qs(40.0, 8.0), 5.0);
assert_eq!(qs(40.0, 0.0), 40.0);
assert_eq!(qs(40.0, -1.0), 40.0);
}
#[test]
fn entropy_uniform_two_symbols() {
assert!((entropy_from_counts(&[5, 5]) - 1.0).abs() < 1e-12);
assert!((entropy_from_counts(&[3, 3, 3, 3]) - 2.0).abs() < 1e-12);
assert_eq!(entropy_from_counts(&[10]), 0.0);
assert_eq!(entropy_from_counts(&[]), 0.0);
assert_eq!(entropy_from_counts(&[0, 0]), 0.0);
}
}