use std::time::Instant;
use crate::adapter::{serialize, Codec};
use crate::bands;
use crate::levels::{self, ComplianceResult};
use crate::metrics;
use crate::report::{BandResult, EcsReport};
const L_TIER_MIN_CR: f64 = 0.8;
fn flatten_i64(signal: &[Vec<i64>]) -> Vec<i64> {
let total: usize = signal.iter().map(|c| c.len()).sum();
let mut out = Vec::with_capacity(total);
for chan in signal {
out.extend_from_slice(chan);
}
out
}
fn flatten_f64(signal: &[Vec<i64>]) -> Vec<f64> {
let total: usize = signal.iter().map(|c| c.len()).sum();
let mut out = Vec::with_capacity(total);
for chan in signal {
out.extend(chan.iter().map(|&s| s as f64));
}
out
}
fn raw_bytes(signal: &[Vec<i64>]) -> u64 {
serialize(signal).len() as u64
}
pub fn run(codec: &dyn Codec, signal: &[Vec<i64>], fs: f64) -> EcsReport {
run_measured(codec, signal, fs, 1)
}
fn median(xs: &mut [f64]) -> f64 {
if xs.is_empty() {
return 0.0;
}
xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = xs.len();
if n % 2 == 1 {
xs[n / 2]
} else {
0.5 * (xs[n / 2 - 1] + xs[n / 2])
}
}
pub fn run_measured(
codec: &dyn Codec,
signal: &[Vec<i64>],
fs: f64,
repeats: usize,
) -> EcsReport {
let raw = raw_bytes(signal);
let reps = repeats.max(1);
if reps > 1 {
let _ = codec.decode(&codec.encode(signal, fs));
}
let mut blob = Vec::new();
let mut recon = Vec::new();
let mut rates = Vec::with_capacity(reps);
for _ in 0..reps {
let t = Instant::now();
blob = codec.encode(signal, fs);
recon = codec.decode(&blob);
let secs = t.elapsed().as_secs_f64();
rates.push(if secs > 0.0 {
(raw as f64 / (1024.0 * 1024.0)) / secs
} else {
0.0
});
}
let comp = blob.len() as u64;
let cr = metrics::compression_ratio(raw, comp);
let throughput_mibs = {
let m = median(&mut rates);
if m.is_finite() {
(m * 1000.0).round() / 1000.0
} else {
0.0
}
};
let peak_bytes = raw.max(comp);
let orig_i = flatten_i64(signal);
let recon_i = flatten_i64(&recon);
let bit_exact = metrics::prd_is_exact_zero(&orig_i, &recon_i);
if bit_exact && cr >= L_TIER_MIN_CR {
let result = levels::grade(1.0, 0.0, cr, 0.0, &[]);
debug_assert_eq!(result.grade, 'L', "exact + cr>=0.8 must grade L");
return EcsReport {
spec_version: crate::SPEC_VERSION.to_string(),
codec: codec.name().to_string(),
dataset: "(single-signal)".to_string(),
n_files: 1,
bit_exact: true,
grade: result.grade,
cr,
prd: 0.0,
prdn: 0.0,
r: 1.0,
snr_db: 120.0,
qs: metrics::qs(cr, 0.0),
per_band: lossless_per_band(),
throughput_mibs,
peak_bytes,
violations: result.violations,
};
}
let orig_f = flatten_f64(signal);
let recon_f = flatten_f64(&recon);
let prd = metrics::prd(&orig_f, &recon_f);
let prdn = metrics::prdn(&orig_f, &recon_f);
let r = metrics::pearson_r(&orig_f, &recon_f);
let snr_db = metrics::snr_db(&orig_f, &recon_f);
let band_rows = bands::per_band_fidelity(&orig_f, &recon_f, fs);
let per_band: Vec<BandResult> = band_rows
.iter()
.map(|(name, br, bp, bs)| BandResult::new(name.clone(), *br, *bp, *bs))
.collect();
let grade_bands: Vec<(String, f64, f64)> = band_rows
.iter()
.map(|(name, br, bp, _)| (name.clone(), *br, *bp))
.collect();
let result: ComplianceResult = levels::grade(r, prd, cr, snr_db, &grade_bands);
EcsReport {
spec_version: crate::SPEC_VERSION.to_string(),
codec: codec.name().to_string(),
dataset: "(single-signal)".to_string(),
n_files: 1,
bit_exact: false,
grade: result.grade,
cr,
prd,
prdn,
r,
snr_db,
qs: metrics::qs(cr, prd),
per_band,
throughput_mibs,
peak_bytes,
violations: result.violations,
}
}
fn lossless_per_band() -> Vec<BandResult> {
bands::clinical_band_names()
.into_iter()
.map(|name| BandResult::new(name, 1.0, 0.0, 120.0))
.collect()
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CorpusSummary {
pub codec: String,
pub n_files: usize,
pub mean_cr: f64,
pub mean_prd: f64,
pub mean_r: f64,
pub worst_grade: char,
pub all_bit_exact: bool,
}
fn grade_rank(g: char) -> u8 {
match g {
'L' => 0,
'N' => 1,
'C' => 2,
'M' => 3,
'A' => 4,
_ => 5,
}
}
pub fn run_corpus(
codec: &dyn Codec,
files: &[(Vec<Vec<i64>>, f64)],
) -> (Vec<EcsReport>, CorpusSummary) {
let per_file: Vec<(EcsReport, u64, u64)> = files
.iter()
.map(|(signal, fs)| {
let rep = run(codec, signal, *fs);
let raw = raw_bytes(signal);
let comp = if rep.cr > 0.0 {
(raw as f64 / rep.cr).round() as u64
} else {
raw
};
(rep, raw, comp)
})
.collect();
let summary = summarize(codec.name(), &per_file);
let reports = per_file.into_iter().map(|(r, _, _)| r).collect();
(reports, summary)
}
pub fn summarize(codec: &str, per_file: &[(EcsReport, u64, u64)]) -> CorpusSummary {
let n = per_file.len();
let cr_pairs: Vec<(u64, u64)> =
per_file.iter().map(|(_, raw, comp)| (*raw, (*comp).max(1))).collect();
let mean_cr = metrics::aggregate_cr(&cr_pairs);
let sum_prd: f64 = per_file.iter().map(|(r, _, _)| r.prd).sum();
let sum_r: f64 = per_file.iter().map(|(r, _, _)| r.r).sum();
let (mean_prd, mean_r) = if n > 0 {
(sum_prd / n as f64, sum_r / n as f64)
} else {
(0.0, 0.0)
};
let mut worst_rank = 0u8;
let mut worst_grade = if n == 0 { '\0' } else { 'L' };
let mut all_bit_exact = true;
for (rep, _, _) in per_file {
if !rep.bit_exact {
all_bit_exact = false;
}
let rank = grade_rank(rep.grade);
if rank >= worst_rank {
worst_rank = rank;
worst_grade = rep.grade;
}
}
CorpusSummary {
codec: codec.to_string(),
n_files: n,
mean_cr,
mean_prd,
mean_r,
worst_grade,
all_bit_exact,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::adapter::{Gzip, Store};
fn fixture() -> Vec<Vec<i64>> {
let fs = 256.0;
let n = 256;
let ch = |scale: f64| -> Vec<i64> {
(0..n)
.map(|i| {
let t = i as f64 / fs;
let v = scale
* (100.0 * (2.0 * std::f64::consts::PI * 2.0 * t).sin()
+ 60.0 * (2.0 * std::f64::consts::PI * 10.0 * t).sin()
+ 30.0 * (2.0 * std::f64::consts::PI * 40.0 * t).sin());
v.round() as i64
})
.collect()
};
vec![ch(1.0), ch(1.5), ch(0.7)]
}
#[test]
fn store_is_lossless_grade_l() {
let sig = fixture();
let rep = run(&Store, &sig, 256.0);
assert!(rep.bit_exact, "store must be bit-exact");
assert_eq!(rep.grade, 'L');
assert_eq!(rep.prd, 0.0);
assert_eq!(rep.r, 1.0);
assert!(rep.cr >= 0.8, "store cr {} below L floor", rep.cr);
assert_eq!(rep.per_band.len(), bands::CLINICAL_BANDS.len());
assert!(rep.throughput_mibs.is_finite());
}
#[test]
fn gzip_is_lossless_grade_l() {
let sig = fixture();
let rep = run(&Gzip, &sig, 256.0);
assert!(rep.bit_exact, "gzip must be bit-exact");
assert_eq!(rep.grade, 'L');
}
#[test]
fn run_corpus_aggregates() {
let files = vec![(fixture(), 256.0), (fixture(), 256.0)];
let (reports, summary) = run_corpus(&Store, &files);
assert_eq!(reports.len(), 2);
assert_eq!(summary.n_files, 2);
assert_eq!(summary.worst_grade, 'L');
assert!(summary.all_bit_exact);
assert!(summary.mean_cr >= 0.8);
}
#[test]
fn empty_corpus_is_below_floor() {
let (reports, summary) = run_corpus(&Store, &[]);
assert!(reports.is_empty());
assert_eq!(summary.n_files, 0);
assert_eq!(summary.worst_grade, '\0');
}
}