helena 0.1.0

Core types and component interfaces for helena, a latent data-to-waveform generation platform.
Documentation
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;

/// An ordered conditioning sweep's effect on generator outputs.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ResponseCurve {
    net_response: f32,
    monotonicity: f32,
    step_count: usize,
}

impl ResponseCurve {
    /// Measure response over `sweep`, where each step contains seed samples.
    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(),
        })
    }

    /// L2 displacement from the first to last sweep step.
    pub fn net_response(&self) -> f32 {
        self.net_response
    }

    /// Net displacement over total path length, in `[0, 1]`.
    pub fn monotonicity(&self) -> f32 {
        self.monotonicity
    }

    /// Number of edit steps in the sweep.
    pub fn step_count(&self) -> usize {
        self.step_count
    }
}

/// Sample an ordered conditioning sweep from `generator` and score it.
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)
}