use std::num::NonZeroUsize;
use proptest::prelude::*;
use crate::diffusion::NoiseLevel;
use crate::eval::ClusterBootstrap;
use super::*;
fn level(gamma: f32) -> NoiseLevel {
NoiseLevel::from_signal_variance(gamma).unwrap()
}
fn bootstrap() -> ClusterBootstrap {
ClusterBootstrap::new(NonZeroUsize::new(256).unwrap(), 0.95, 7).unwrap()
}
fn loss_at(gamma: f32, loss: f32) -> CleanTargetLoss {
let target = Tensor::vector(vec![0.0]);
let estimate = Tensor::vector(vec![loss.sqrt()]);
CleanTargetLoss::from_estimate(level(gamma), &estimate, &target).unwrap()
}
fn bins(n: usize) -> NonZeroUsize {
NonZeroUsize::new(n).unwrap()
}
#[test]
fn estimate_equal_to_target_has_zero_loss() {
let t = Tensor::new([2, 2], vec![1.0, -2.0, 3.0, 0.5]).unwrap();
let l = CleanTargetLoss::from_estimate(level(0.5), &t, &t).unwrap();
assert_eq!(l.loss(), 0.0);
assert_eq!(l.level(), level(0.5));
}
#[test]
fn estimate_loss_matches_hand_computed_mse() {
let estimate = Tensor::new([1, 2], vec![1.0, 4.0]).unwrap();
let target = Tensor::new([1, 2], vec![0.0, 0.0]).unwrap();
let l = CleanTargetLoss::from_estimate(level(0.3), &estimate, &target).unwrap();
assert!((l.loss() - 8.5).abs() < 1e-9);
}
#[test]
fn from_velocity_recovers_the_true_target_exactly() {
let x0 = Tensor::new([2, 2], vec![0.7, -0.3, 0.4, 0.1]).unwrap();
let eps = Tensor::new([2, 2], vec![-1.0, 0.5, 0.2, -0.8]).unwrap();
for gamma in [0.05, 0.5, 0.95] {
let lvl = level(gamma);
let x_t = lvl.diffuse(&x0, &eps).unwrap();
let v = lvl.velocity_target(&x0, &eps).unwrap();
let l = CleanTargetLoss::from_velocity(lvl, &x_t, &v, &x0).unwrap();
assert!(l.loss() < 1e-9, "gamma {gamma}: loss {}", l.loss());
}
}
#[test]
fn high_snr_recovery_is_target_regardless_of_velocity() {
let x0 = Tensor::new([1, 3], vec![0.2, -0.5, 0.9]).unwrap();
let eps = Tensor::zeros([1, 3]).unwrap();
let lvl = NoiseLevel::pure_signal();
let x_t = lvl.diffuse(&x0, &eps).unwrap();
let arbitrary_v = Tensor::new([1, 3], vec![5.0, -7.0, 3.0]).unwrap();
let l = CleanTargetLoss::from_velocity(lvl, &x_t, &arbitrary_v, &x0).unwrap();
assert!(l.loss() < 1e-12, "loss {}", l.loss());
}
#[test]
fn from_estimate_rejects_shape_mismatch() {
let a = Tensor::new([1, 2], vec![0.0, 0.0]).unwrap();
let b = Tensor::new([2, 1], vec![0.0, 0.0]).unwrap();
assert!(matches!(
CleanTargetLoss::from_estimate(level(0.5), &a, &b),
Err(Error::Validation(_))
));
}
#[test]
fn from_estimate_rejects_non_finite_and_empty() {
let target = Tensor::new([1, 2], vec![0.0, 0.0]).unwrap();
let nan = Tensor::new([1, 2], vec![f32::NAN, 1.0]).unwrap();
assert!(matches!(
CleanTargetLoss::from_estimate(level(0.5), &nan, &target),
Err(Error::Validation(_))
));
let empty = Tensor::new([0, 2], vec![]).unwrap();
assert!(matches!(
CleanTargetLoss::from_estimate(level(0.5), &empty, &empty),
Err(Error::Validation(_))
));
}
#[test]
fn curve_rejects_empty_losses() {
assert!(matches!(
PerSnrLossCurve::measure(&[], bins(4)),
Err(Error::Validation(_))
));
}
#[test]
fn curve_assigns_losses_to_expected_bins() {
let losses = [
loss_at(0.1, 1.0), loss_at(0.3, 4.0), loss_at(0.6, 9.0), loss_at(0.9, 16.0), loss_at(1.0, 4.0), ];
let curve = PerSnrLossCurve::measure(&losses, bins(4)).unwrap();
assert_eq!(curve.observation_count(), 5);
let b = curve.bins();
assert_eq!(b.len(), 4);
assert_eq!(b[0].count(), 1);
assert_eq!(b[0].mean_loss(), Some(1.0));
assert_eq!(b[1].mean_loss(), Some(4.0));
assert_eq!(b[2].mean_loss(), Some(9.0));
assert_eq!(b[3].count(), 2);
assert_eq!(b[3].mean_loss(), Some(10.0)); }
#[test]
fn empty_bins_report_no_mean() {
let curve = PerSnrLossCurve::measure(&[loss_at(0.6, 4.0)], bins(4)).unwrap();
let b = curve.bins();
assert_eq!(b[0].count(), 0);
assert_eq!(b[0].mean_loss(), None);
assert_eq!(b[2].mean_loss(), Some(4.0));
}
#[test]
fn bin_ranges_partition_the_unit_interval() {
let curve = PerSnrLossCurve::measure(&[loss_at(0.5, 1.0)], bins(5)).unwrap();
let b = curve.bins();
assert_eq!(b[0].range().0, 0.0);
assert!((b[4].range().1 - 1.0).abs() < 1e-6);
for w in b.windows(2) {
assert_eq!(w[0].range().1, w[1].range().0);
}
}
#[test]
fn bin_intervals_center_on_bin_means() {
let per_item = vec![
vec![loss_at(0.1, 1.0), loss_at(0.6, 4.0)],
vec![loss_at(0.1, 9.0), loss_at(0.6, 16.0)],
];
let curve = PerSnrLossCurve::measure(&per_item.concat(), bins(4)).unwrap();
let intervals = PerSnrLossCurve::bin_intervals(&per_item, bins(4), &bootstrap()).unwrap();
assert_eq!(intervals.len(), curve.bins().len());
for (bin, ci) in curve.bins().iter().zip(&intervals) {
match (bin.mean_loss(), ci) {
(Some(mean), Some(ci)) => {
assert!(
(ci.point() - mean).abs() < 1e-12,
"{} vs {mean}",
ci.point()
);
assert!(ci.lo() <= ci.point() && ci.point() <= ci.hi());
}
(None, None) => {}
other => panic!("bin/interval populated-ness disagree: {other:?}"),
}
}
assert_eq!(intervals[0].unwrap().point(), 5.0);
assert_eq!(intervals[2].unwrap().point(), 10.0);
assert!(intervals[1].is_none() && intervals[3].is_none());
}
#[test]
fn bin_intervals_center_on_pooled_mean_under_unequal_items() {
let per_item = vec![
vec![loss_at(0.1, 1.0), loss_at(0.1, 1.0)], vec![loss_at(0.1, 16.0)], ];
let curve = PerSnrLossCurve::measure(&per_item.concat(), bins(4)).unwrap();
let intervals = PerSnrLossCurve::bin_intervals(&per_item, bins(4), &bootstrap()).unwrap();
assert_eq!(curve.bins()[0].mean_loss(), Some(6.0));
let ci = intervals[0].unwrap();
assert!(
(ci.point() - 6.0).abs() < 1e-12,
"point {} should equal the pooled bin mean 6.0",
ci.point()
);
assert!(ci.lo() <= ci.point() && ci.point() <= ci.hi());
assert!(intervals[1].is_none() && intervals[2].is_none() && intervals[3].is_none());
}
#[test]
fn bin_intervals_reject_no_losses() {
assert!(matches!(
PerSnrLossCurve::bin_intervals(&[], bins(4), &bootstrap()),
Err(Error::Validation(_))
));
assert!(matches!(
PerSnrLossCurve::bin_intervals(&[vec![], vec![]], bins(4), &bootstrap()),
Err(Error::Validation(_))
));
}
proptest! {
#[test]
fn true_velocity_recovery_is_exact(
x0 in proptest::collection::vec(-5.0f32..5.0, 4),
eps in proptest::collection::vec(-5.0f32..5.0, 4),
gamma in 0.0f32..=1.0,
) {
let x0 = Tensor::new([2, 2], x0).unwrap();
let eps = Tensor::new([2, 2], eps).unwrap();
let lvl = level(gamma);
let x_t = lvl.diffuse(&x0, &eps).unwrap();
let v = lvl.velocity_target(&x0, &eps).unwrap();
let l = CleanTargetLoss::from_velocity(lvl, &x_t, &v, &x0).unwrap();
prop_assert!(l.loss() < 1e-4, "loss {} at gamma {gamma}", l.loss());
}
#[test]
fn curve_conserves_observations(
gammas in proptest::collection::vec(0.0f32..=1.0, 1..32),
n in 1usize..8,
) {
let losses: Vec<CleanTargetLoss> = gammas.iter().map(|&g| loss_at(g, 1.0)).collect();
let curve = PerSnrLossCurve::measure(&losses, bins(n)).unwrap();
prop_assert_eq!(curve.observation_count(), losses.len());
prop_assert_eq!(curve.bins().len(), n);
}
#[test]
fn bin_means_are_bounded_by_their_members(
values in proptest::collection::vec((0.0f32..=1.0, 0.0f32..9.0), 1..24),
n in 1usize..6,
) {
let losses: Vec<CleanTargetLoss> =
values.iter().map(|&(g, v)| loss_at(g, v)).collect();
let curve = PerSnrLossCurve::measure(&losses, bins(n)).unwrap();
for bin in curve.bins() {
if let Some(mean) = bin.mean_loss() {
prop_assert!((0.0..=9.0 + 1e-3).contains(&mean), "mean {mean}");
} else {
prop_assert_eq!(bin.count(), 0);
}
}
}
}