use super::*;
use crate::audio::ced::{NUM_CLASSES, WINDOW_SAMPLES, prediction::sigmoid, window::Span};
fn window(i: usize, v0: f32, v1: f32) -> WindowConfidences {
let mut values = vec![0.25f32; NUM_CLASSES];
values[0] = v0;
values[1] = v1;
WindowConfidences::new(
Confidences::new(values),
Span::new(i * WINDOW_SAMPLES, WINDOW_SAMPLES, WINDOW_SAMPLES),
)
}
#[test]
fn mean_is_the_equal_weight_arithmetic_mean() {
let windows = [window(0, 0.2, 0.9), window(1, 0.6, 0.1)];
let agg = aggregate_windows(ChunkAggregation::Mean, &windows).unwrap();
assert!((agg.as_slice()[0] - 0.4).abs() < 1e-6);
assert!((agg.as_slice()[1] - 0.5).abs() < 1e-6);
assert!((agg.as_slice()[2] - 0.25).abs() < 1e-6);
}
#[test]
fn max_is_the_elementwise_peak() {
let windows = [window(0, 0.2, 0.9), window(1, 0.6, 0.1)];
let agg = aggregate_windows(ChunkAggregation::Max, &windows).unwrap();
assert_eq!(agg.as_slice()[0], 0.6);
assert_eq!(agg.as_slice()[1], 0.9);
assert_eq!(agg.as_slice()[2], 0.25);
}
#[test]
fn mean_and_max_discriminate() {
let windows = [window(0, 0.2, 0.9), window(1, 0.6, 0.1)];
let mean = aggregate_windows(ChunkAggregation::Mean, &windows).unwrap();
let max = aggregate_windows(ChunkAggregation::Max, &windows).unwrap();
assert_ne!(mean.as_slice()[0], max.as_slice()[0]);
}
#[test]
fn single_window_is_the_bit_exact_identity() {
let w = window(0, 0.123_456_7, 0.9);
for aggregation in [ChunkAggregation::Mean, ChunkAggregation::Max] {
let agg = aggregate_windows(aggregation, std::slice::from_ref(&w)).unwrap();
assert_eq!(agg, *w.value(), "{aggregation:?}");
}
}
#[test]
fn empty_windows_is_a_typed_error() {
let err = aggregate_windows(ChunkAggregation::Mean, &[]).unwrap_err();
assert!(
matches!(err, crate::audio::ced::Error::EmptyWindows),
"got {err:?}"
);
}
#[test]
fn aggregation_runs_in_confidence_space_not_logit_space() {
let mut l0 = vec![0.0f32; NUM_CLASSES];
let mut l1 = vec![0.0f32; NUM_CLASSES];
l0[0] = 0.0;
l1[0] = 2.0;
let windows = [
WindowConfidences::new(
Confidences::from_logits(&l0),
Span::new(0, WINDOW_SAMPLES, WINDOW_SAMPLES),
),
WindowConfidences::new(
Confidences::from_logits(&l1),
Span::new(WINDOW_SAMPLES, WINDOW_SAMPLES, WINDOW_SAMPLES),
),
];
let agg = aggregate_windows(ChunkAggregation::Mean, &windows).unwrap();
let confidence_space = (sigmoid(0.0) + sigmoid(2.0)) / 2.0;
let logit_space = sigmoid(1.0);
assert!((agg.as_slice()[0] - confidence_space).abs() < 1e-6);
assert!(
(agg.as_slice()[0] - logit_space).abs() > 0.02,
"confidence-space and logit-space aggregation must be distinguishable"
);
}
#[test]
fn accumulator_finish_without_pushes_is_empty_windows() {
for aggregation in [ChunkAggregation::Mean, ChunkAggregation::Max] {
let err = Accumulator::new(aggregation).finish().unwrap_err();
assert!(
matches!(err, crate::audio::ced::Error::EmptyWindows),
"{aggregation:?}: got {err:?}"
);
}
}
#[test]
fn accumulator_matches_aggregate_windows_bit_exactly() {
let windows = [
window(0, 0.2, 0.9),
window(1, 0.6, 0.1),
window(2, 0.123_456_7, 0.000_001),
];
for aggregation in [ChunkAggregation::Mean, ChunkAggregation::Max] {
let batch = aggregate_windows(aggregation, &windows).unwrap();
let mut acc = Accumulator::new(aggregation);
for w in &windows {
acc.push(w.value());
}
let streamed = acc.finish().unwrap();
assert_eq!(streamed.as_slice(), batch.as_slice(), "{aggregation:?}");
}
}
#[cfg(feature = "serde")]
mod serde_tests {
use super::*;
#[test]
fn wire_spellings_are_pinned() {
for kind in [ChunkAggregation::Mean, ChunkAggregation::Max] {
let expected = match kind {
ChunkAggregation::Mean => "\"mean\"",
ChunkAggregation::Max => "\"max\"",
};
assert_eq!(serde_json::to_string(&kind).unwrap(), expected);
let back: ChunkAggregation = serde_json::from_str(expected).unwrap();
assert_eq!(back, kind);
}
}
#[test]
fn default_is_mean() {
assert_eq!(ChunkAggregation::default(), ChunkAggregation::Mean);
}
#[test]
fn unknown_spelling_is_rejected() {
assert!(serde_json::from_str::<ChunkAggregation>("\"median\"").is_err());
assert!(serde_json::from_str::<ChunkAggregation>("\"Mean\"").is_err());
}
}