use std::fmt::Debug;
use fastrand::Rng;
use crate::{
constants::{ConstSqrt5, ConstThreeQuarters},
kernel::{Density, Kernel, Sample},
traits::{
loopback::{SelfAdd, SelfDiv, SelfMul, SelfNeg, SelfSub},
shortcuts::Multiplicative,
},
};
#[derive(Copy, Clone, Debug)]
pub struct Epanechnikov<T> {
location: T,
std: T,
}
impl<T> Density for Epanechnikov<T>
where
T: SelfSub
+ Multiplicative
+ Copy
+ PartialOrd
+ SelfNeg
+ SelfDiv
+ num_traits::One
+ num_traits::Zero
+ ConstSqrt5
+ ConstThreeQuarters,
{
type Param = T;
type Output = T;
fn density(&self, at: Self::Param) -> Self::Output {
let normalized = (at - self.location) / self.std / T::SQRT_5;
if (-T::one()..=T::one()).contains(&normalized) {
T::THREE_QUARTERS / T::SQRT_5 * (T::one() - normalized * normalized) / self.std
} else {
T::zero()
}
}
}
impl<T> Sample for Epanechnikov<T>
where
T: Copy + SelfAdd + SelfMul + TryFrom<f64>,
<T as TryFrom<f64>>::Error: Debug,
{
type Param = T;
fn sample(&self, rng: &mut Rng) -> Self::Param {
let (x1, x2) = min_2(rng.f64(), rng.f64(), rng.f64());
let abs_normalized = if rng.bool() { x1 } else { x2 };
let normalized = if rng.bool() {
abs_normalized
} else {
-abs_normalized
};
self.location + self.std * T::try_from(normalized * f64::SQRT_5).unwrap()
}
}
impl<T> Kernel for Epanechnikov<T>
where
Self: Density<Param = T, Output = T> + Sample<Param = T>,
T: PartialOrd + num_traits::Zero,
{
type Param = T;
fn new(location: T, std: T) -> Self {
assert!(std > T::zero());
Self { location, std }
}
}
impl<T> Default for Epanechnikov<T>
where
T: num_traits::Zero + num_traits::One,
{
fn default() -> Self {
Self {
location: T::zero(),
std: T::one(),
}
}
}
fn min_2<T: PartialOrd>(mut x1: T, mut x2: T, x3: T) -> (T, T) {
if x1 > x2 {
(x1, x2) = (x2, x1);
}
(x1, if x2 > x3 { x3 } else { x2 })
}
#[cfg(test)]
mod tests {
use approx::assert_abs_diff_eq;
use super::*;
#[test]
fn density_inside_ok() {
let kernel = Epanechnikov::<f64>::default();
assert_abs_diff_eq!(kernel.density(0.0), 0.335_410_196_624_968_46);
assert_abs_diff_eq!(kernel.density(f64::SQRT_5), 0.0);
assert_abs_diff_eq!(kernel.density(-f64::SQRT_5), 0.0);
}
#[test]
#[allow(clippy::float_cmp)]
fn density_outside_ok() {
let kernel = Epanechnikov::<f64>::default();
assert_eq!(kernel.density(-10.0), 0.0);
assert_eq!(kernel.density(10.0), 0.0);
}
#[test]
fn min_2_ok() {
assert_eq!(min_2(1, 2, 3), (1, 2));
assert_eq!(min_2(1, 3, 2), (1, 2));
assert_eq!(min_2(2, 1, 3), (1, 2));
assert_eq!(min_2(2, 3, 1), (2, 1));
assert_eq!(min_2(3, 1, 2), (1, 2));
assert_eq!(min_2(3, 2, 1), (2, 1));
}
}