helena 0.1.0

Core types and component interfaces for helena, a latent data-to-waveform generation platform.
Documentation
use super::*;

fn level(gamma: f32) -> NoiseLevel {
    NoiseLevel::from_signal_variance(gamma).unwrap()
}

#[test]
fn interval_rejects_out_of_range_and_degenerate_bounds() {
    // Bounds outside [0, 1] or non-finite.
    for (lo, hi) in [
        (-0.1, 0.5),
        (0.2, 1.1),
        (f32::NAN, 0.5),
        (0.2, f32::INFINITY),
    ] {
        assert!(matches!(
            GuidanceInterval::new(lo, hi),
            Err(Error::Validation(_))
        ));
    }
    // An empty or inverted band (lo >= hi) is unrepresentable.
    for (lo, hi) in [(0.6, 0.6), (0.8, 0.2)] {
        assert!(matches!(
            GuidanceInterval::new(lo, hi),
            Err(Error::Validation(_))
        ));
    }
    let band = GuidanceInterval::new(0.2, 0.8).unwrap();
    assert_eq!((band.lo(), band.hi()), (0.2, 0.8));
}

#[test]
fn default_is_constant() {
    assert_eq!(GuidanceSchedule::default(), GuidanceSchedule::Constant);
}

#[test]
fn constant_returns_the_base_scale_unchanged() {
    // Bit for bit, so an unscheduled run is exactly the pre-scheduling sampler.
    for gamma in [0.0, 0.3, 0.5, 1.0] {
        for w in [1.0, 2.5, 0.4] {
            assert_eq!(GuidanceSchedule::Constant.guidance_at(level(gamma), w), w);
        }
    }
}

#[test]
fn interval_keeps_full_guidance_inside_and_passes_through_outside() {
    let schedule = GuidanceSchedule::Interval(GuidanceInterval::new(0.25, 0.75).unwrap());
    let w = 3.0;
    // Inside the closed band keeps the base scale.
    for gamma in [0.25, 0.5, 0.75] {
        assert_eq!(schedule.guidance_at(level(gamma), w), w);
    }
    // Outside passes through to neutral 1.0 (pure conditional).
    for gamma in [0.0, 0.1, 0.9, 1.0] {
        assert_eq!(schedule.guidance_at(level(gamma), w), 1.0);
    }
}

#[test]
fn monotone_ramps_from_neutral_at_high_noise_to_base_at_low_noise() {
    let schedule = GuidanceSchedule::Monotone;
    let w = 5.0;
    // gamma = 0 is the highest noise (chain start): neutral.
    assert_eq!(schedule.guidance_at(level(0.0), w), 1.0);
    // gamma = 1 is the lowest noise (chain end): the full base scale.
    assert_eq!(schedule.guidance_at(level(1.0), w), w);
    // The interior is the convex blend, non-decreasing in gamma for w > 1.
    assert!((schedule.guidance_at(level(0.5), w) - (1.0 + (w - 1.0) * 0.5)).abs() < 1e-6);
    assert!(schedule.guidance_at(level(0.2), w) <= schedule.guidance_at(level(0.6), w));
}

#[test]
fn every_schedule_is_inert_at_neutral_base_guidance() {
    // With base guidance 1.0 there is nothing to schedule: every arm returns the
    // neutral pass-through, so the sampler's unconditional pass stays unneeded.
    let band = GuidanceSchedule::Interval(GuidanceInterval::new(0.1, 0.9).unwrap());
    for gamma in [0.0, 0.4, 1.0] {
        assert_eq!(
            GuidanceSchedule::Constant.guidance_at(level(gamma), 1.0),
            1.0
        );
        assert_eq!(
            GuidanceSchedule::Monotone.guidance_at(level(gamma), 1.0),
            1.0
        );
        assert_eq!(band.guidance_at(level(gamma), 1.0), 1.0);
    }
}

#[test]
fn serde_round_trips_each_variant() {
    for schedule in [
        GuidanceSchedule::Constant,
        GuidanceSchedule::Monotone,
        GuidanceSchedule::Interval(GuidanceInterval::new(0.2, 0.8).unwrap()),
    ] {
        let json = serde_json::to_string(&schedule).unwrap();
        assert_eq!(
            serde_json::from_str::<GuidanceSchedule>(&json).unwrap(),
            schedule
        );
    }
    // Unit variants carry just the flat discriminant.
    assert_eq!(
        serde_json::to_string(&GuidanceSchedule::Constant).unwrap(),
        r#"{"type":"constant"}"#
    );
    // The interval is adjacently tagged with its band.
    let parsed: GuidanceSchedule =
        serde_json::from_str(r#"{"type":"interval","band":{"lo":0.2,"hi":0.8}}"#).unwrap();
    assert_eq!(
        parsed,
        GuidanceSchedule::Interval(GuidanceInterval::new(0.2, 0.8).unwrap())
    );
}

#[test]
fn deserialization_validates_band_and_fails_closed() {
    // An invalid band is rejected on deserialize, not only via `new`.
    assert!(
        serde_json::from_str::<GuidanceSchedule>(
            r#"{"type":"interval","band":{"lo":0.9,"hi":0.2}}"#
        )
        .is_err()
    );
    // A typo'd band field fails closed rather than being silently ignored.
    assert!(
        serde_json::from_str::<GuidanceSchedule>(
            r#"{"type":"interval","band":{"lo":0.2,"hi":0.8,"mid":0.5}}"#
        )
        .is_err()
    );
    // A stray field beside the tag on a unit variant fails closed too, so the
    // object is fail-closed at every depth, not only inside the band.
    assert!(serde_json::from_str::<GuidanceSchedule>(r#"{"type":"constant","mid":0.5}"#).is_err());
    // An unknown discriminant is rejected.
    assert!(serde_json::from_str::<GuidanceSchedule>(r#"{"type":"cosine"}"#).is_err());
}