use std::f32::consts::FRAC_PI_2;
use serde::{Deserialize, Serialize};
use crate::{Error, Result};
use super::{level::NoiseLevel, ops::canonical_zero};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "CosineScheduleRepr")]
pub struct CosineSchedule {
offset: f32,
}
#[derive(Deserialize)]
struct CosineScheduleRepr {
offset: f32,
}
impl TryFrom<CosineScheduleRepr> for CosineSchedule {
type Error = Error;
fn try_from(repr: CosineScheduleRepr) -> Result<Self> {
Self::new(repr.offset)
}
}
impl Default for CosineSchedule {
fn default() -> Self {
Self {
offset: Self::DEFAULT_OFFSET,
}
}
}
impl CosineSchedule {
pub const DEFAULT_OFFSET: f32 = 0.008;
pub const MAX_OFFSET: f32 = 1.0;
pub fn new(offset: f32) -> Result<Self> {
if !offset.is_finite() || !(0.0..=Self::MAX_OFFSET).contains(&offset) {
return Err(Error::validation(format!(
"cosine schedule offset must be finite and in [0, {}], got {offset}",
Self::MAX_OFFSET
)));
}
Ok(Self {
offset: canonical_zero(offset),
})
}
pub fn signal_variance(&self, t: f32) -> Result<f32> {
if !t.is_finite() || !(0.0..=1.0).contains(&t) {
return Err(Error::validation(format!(
"schedule time must be finite and in [0, 1], got {t}"
)));
}
Ok((self.raised_cos(t) / self.raised_cos(0.0)).clamp(0.0, 1.0))
}
pub fn level(&self, t: f32) -> Result<NoiseLevel> {
NoiseLevel::from_signal_variance(self.signal_variance(t)?)
}
fn raised_cos(&self, t: f32) -> f32 {
(((t + self.offset) / (1.0 + self.offset)) * FRAC_PI_2)
.cos()
.powi(2)
}
}