use crate::latent::{Continuous, FrameSeq};
use crate::{Conditioning, Error, GenerationParams, Latent, LatentGenerator, Result, Seed};
use super::super::common::{continuous_set, elementwise_mean, l2_distance, shape};
use super::sampling::generate_grid;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ResponseCurve {
net_response: f32,
monotonicity: f32,
step_count: usize,
}
impl ResponseCurve {
pub fn measure(sweep: &[Vec<Latent<Continuous>>]) -> Result<Self> {
if sweep.len() < 2 {
return Err(Error::validation(
"a response curve needs at least two sweep steps",
));
}
let mut means: Vec<FrameSeq> = Vec::with_capacity(sweep.len());
let mut expected_shape: Option<[usize; 2]> = None;
for step in sweep {
if step.is_empty() {
return Err(Error::validation("a sweep step has no samples"));
}
let seqs = continuous_set(step)?;
match &expected_shape {
None => expected_shape = Some(shape(seqs[0])),
Some(expected) if *expected != shape(seqs[0]) => {
return Err(Error::validation(format!(
"all sweep steps must share a shape; expected {expected:?}, got {:?}",
shape(seqs[0])
)));
}
_ => {}
}
means.push(elementwise_mean(&seqs));
}
let path: f64 = means.windows(2).map(|p| l2_distance(&p[0], &p[1])).sum();
let net = l2_distance(&means[0], &means[means.len() - 1]);
let monotonicity = if path > 0.0 { (net / path) as f32 } else { 0.0 };
Ok(Self {
net_response: net as f32,
monotonicity,
step_count: sweep.len(),
})
}
pub fn net_response(&self) -> f32 {
self.net_response
}
pub fn monotonicity(&self) -> f32 {
self.monotonicity
}
pub fn step_count(&self) -> usize {
self.step_count
}
}
pub fn sweep_response<G: LatentGenerator<Continuous>>(
generator: &G,
sweep: &[Conditioning],
seeds: &[Seed],
params: &GenerationParams,
) -> Result<ResponseCurve> {
if sweep.len() < 2 {
return Err(Error::validation(
"a response curve needs at least two sweep steps",
));
}
let grid = generate_grid(generator, sweep, seeds, params)?;
ResponseCurve::measure(&grid)
}