use std::num::NonZeroUsize;
use crate::diffusion::NoiseLevel;
use crate::eval::{ClusterBootstrap, ConfidenceInterval};
use crate::{Error, Result, Tensor};
use super::super::common::mean_squared_error;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CleanTargetLoss {
level: NoiseLevel,
loss: f64,
}
impl CleanTargetLoss {
pub fn from_estimate(level: NoiseLevel, x0_hat: &Tensor, target: &Tensor) -> Result<Self> {
let loss = mean_squared_error(x0_hat, target)?;
Ok(Self { level, loss })
}
pub fn from_velocity(
level: NoiseLevel,
x_t: &Tensor,
v_hat: &Tensor,
target: &Tensor,
) -> Result<Self> {
let x0_hat = level.signal_from_velocity(x_t, v_hat)?;
Self::from_estimate(level, &x0_hat, target)
}
pub fn level(&self) -> NoiseLevel {
self.level
}
pub fn loss(&self) -> f64 {
self.loss
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SnrBin {
lo: f32,
hi: f32,
sum: f64,
count: usize,
}
impl SnrBin {
pub fn range(&self) -> (f32, f32) {
(self.lo, self.hi)
}
pub fn count(&self) -> usize {
self.count
}
pub fn mean_loss(&self) -> Option<f64> {
(self.count > 0).then(|| self.sum / self.count as f64)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PerSnrLossCurve {
bins: Vec<SnrBin>,
}
impl PerSnrLossCurve {
pub fn measure(losses: &[CleanTargetLoss], bins: NonZeroUsize) -> Result<Self> {
if losses.is_empty() {
return Err(Error::validation("no clean-target losses to bin"));
}
let n = bins.get();
let mut acc: Vec<SnrBin> = (0..n)
.map(|b| SnrBin {
lo: b as f32 / n as f32,
hi: (b + 1) as f32 / n as f32,
sum: 0.0,
count: 0,
})
.collect();
for loss in losses {
let idx = bin_index(loss.level().signal_variance(), n);
acc[idx].sum += loss.loss();
acc[idx].count += 1;
}
Ok(Self { bins: acc })
}
pub fn bin_intervals(
per_item: &[Vec<CleanTargetLoss>],
bins: NonZeroUsize,
bootstrap: &ClusterBootstrap,
) -> Result<Vec<Option<ConfidenceInterval>>> {
if per_item.iter().all(Vec::is_empty) {
return Err(Error::validation("no clean-target losses to bootstrap"));
}
let n = bins.get();
let mut intervals = Vec::with_capacity(n);
for b in 0..n {
let clusters: Vec<Vec<f64>> = per_item
.iter()
.filter_map(|item| {
let in_bin: Vec<f64> = item
.iter()
.filter(|l| bin_index(l.level().signal_variance(), n) == b)
.map(CleanTargetLoss::loss)
.collect();
(!in_bin.is_empty()).then_some(in_bin)
})
.collect();
intervals.push(if clusters.is_empty() {
None
} else {
Some(bootstrap.measure_pooled(&clusters)?)
});
}
Ok(intervals)
}
pub fn bins(&self) -> &[SnrBin] {
&self.bins
}
pub fn observation_count(&self) -> usize {
self.bins.iter().map(|b| b.count).sum()
}
}
fn bin_index(gamma: f32, n: usize) -> usize {
((gamma * n as f32) as usize).min(n - 1)
}