use cubecl::prelude::*;
use crate::Space;
#[derive(Clone, PartialEq, Debug)]
pub(crate) struct Normalization {
pub taps: TapSupport,
pub guard: DivGuard,
pub over: Space,
}
impl Normalization {
pub(crate) fn new(taps: TapSupport, guard: DivGuard, over: Space) -> Self {
Normalization { taps, guard, over }
}
}
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub struct DivGuard {
pub epsilon: f32,
pub fallback: f32,
}
impl DivGuard {
pub fn new(epsilon: f32, fallback: f32) -> Self {
assert!(
epsilon.is_finite() && epsilon >= 0.0,
"DivGuard: epsilon must be finite and non-negative"
);
assert!(fallback.is_finite(), "DivGuard: fallback must be finite");
DivGuard { epsilon, fallback }
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum TapSupport {
#[default]
InBounds,
Whole,
}
#[cube]
pub(crate) fn guarded_recip<E: Numeric>(d: E, #[comptime] guard: DivGuard) -> E {
let epsilon = E::cast_from(comptime!(guard.epsilon));
let fallback = E::cast_from(comptime!(guard.fallback));
let valid = d.abs() > epsilon;
let safe = select(valid, d, E::from_int(1));
select(valid, E::from_int(1) / safe, fallback)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn division_guard_accepts_finite_non_negative_thresholds() {
DivGuard::new(0.0, 0.0);
DivGuard::new(1.0e-7, -1.0);
}
#[test]
fn division_guard_rejects_invalid_thresholds() {
for epsilon in [-1.0, f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
assert!(
std::panic::catch_unwind(|| DivGuard::new(epsilon, 0.0)).is_err(),
"epsilon {epsilon:?} should be rejected"
);
}
for fallback in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
assert!(
std::panic::catch_unwind(|| DivGuard::new(1.0e-7, fallback)).is_err(),
"fallback {fallback:?} should be rejected"
);
}
}
}