use core::f64::consts::{PI, SQRT_2};
use num_traits::Float;
const SQRT_2PI: f64 = 2.5066282746310005024157652848110452530069867406099_f64;
const SQRT_PI: f64 = 1.772453850905516027298167483341145182797_f64;
const PI_OVER_2: f64 = PI / 2.0;
const GAUSSIAN_CUTOFF: f64 = 6.0;
struct KernelProperties {
integrator: f64,
variance: f64,
roughness: f64,
}
const COSINE_PROPERTIES: KernelProperties = KernelProperties {
integrator: 4.0 / PI,
variance: 4.0 / PI - 32.0 / (PI * PI * PI),
roughness: 1.0,
};
const EPANECHNIKOV_PROPERTIES: KernelProperties = KernelProperties {
integrator: 4.0 / 3.0,
variance: 4.0 / 15.0,
roughness: 16.0 / 15.0,
};
const GAUSSIAN_PROPERTIES: KernelProperties = KernelProperties {
integrator: SQRT_2PI,
variance: SQRT_2 * SQRT_PI,
roughness: SQRT_PI,
};
const BIWEIGHT_PROPERTIES: KernelProperties = KernelProperties {
integrator: 16.0 / 15.0,
variance: 16.0 / 105.0,
roughness: 256.0 / 315.0,
};
const TRIANGULAR_PROPERTIES: KernelProperties = KernelProperties {
integrator: 1.0,
variance: 1.0 / 6.0,
roughness: 2.0 / 3.0,
};
const TRICUBE_PROPERTIES: KernelProperties = KernelProperties {
integrator: 81.0 / 70.0,
variance: 1.0 / 6.0,
roughness: 6_561.0 / 6_916.0,
};
const UNIFORM_PROPERTIES: KernelProperties = KernelProperties {
integrator: 2.0,
variance: 2.0 / 3.0,
roughness: 2.0,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WeightFunction {
Cosine,
Epanechnikov,
Gaussian,
Biweight,
Triangle,
#[default]
Tricube,
Uniform,
}
impl WeightFunction {
#[inline]
pub const fn name(&self) -> &'static str {
match self {
WeightFunction::Cosine => "Cosine",
WeightFunction::Epanechnikov => "Epanechnikov",
WeightFunction::Gaussian => "Gaussian",
WeightFunction::Biweight => "Biweight",
WeightFunction::Triangle => "Triangle",
WeightFunction::Tricube => "Tricube",
WeightFunction::Uniform => "Uniform",
}
}
const fn properties(&self) -> &'static KernelProperties {
match self {
WeightFunction::Cosine => &COSINE_PROPERTIES,
WeightFunction::Epanechnikov => &EPANECHNIKOV_PROPERTIES,
WeightFunction::Gaussian => &GAUSSIAN_PROPERTIES,
WeightFunction::Biweight => &BIWEIGHT_PROPERTIES,
WeightFunction::Triangle => &TRIANGULAR_PROPERTIES,
WeightFunction::Tricube => &TRICUBE_PROPERTIES,
WeightFunction::Uniform => &UNIFORM_PROPERTIES,
}
}
#[inline]
pub fn variance(&self) -> f64 {
self.properties().variance
}
#[inline]
pub fn roughness(&self) -> f64 {
self.properties().roughness
}
#[inline]
pub fn integrator(&self) -> f64 {
self.properties().integrator
}
#[inline]
pub fn efficiency(&self) -> f64 {
let stats = self.properties();
let ep_stats = &EPANECHNIKOV_PROPERTIES;
let r_ratio = ep_stats.roughness / stats.roughness;
let v_ratio = ep_stats.variance / stats.variance;
let c_ratio = stats.integrator / ep_stats.integrator;
r_ratio.powf(4.0 / 5.0) * v_ratio.powf(2.0 / 5.0) * c_ratio.powf(2.0)
}
#[inline]
pub fn support(&self) -> Option<(f64, f64)> {
match self {
WeightFunction::Gaussian => None, _ => Some((-1.0, 1.0)), }
}
#[inline]
fn is_bounded(&self) -> bool {
self.support().is_some()
}
#[inline]
pub fn compute_weight<T: Float>(&self, u: T) -> T {
let abs_u = u.abs();
if self.is_bounded() && abs_u >= T::one() {
return T::zero();
}
match self {
WeightFunction::Cosine => {
let pi_over_2 = T::from(PI_OVER_2).unwrap();
(pi_over_2 * abs_u).cos()
}
WeightFunction::Epanechnikov => T::one() - abs_u * abs_u,
WeightFunction::Gaussian => {
let u_f64 = abs_u.to_f64().unwrap_or(f64::INFINITY);
if u_f64 > GAUSSIAN_CUTOFF {
T::from(f64::MIN_POSITIVE).unwrap_or_else(T::zero)
} else {
let val = (-0.5 * u_f64 * u_f64).exp().max(f64::MIN_POSITIVE);
T::from(val).unwrap_or_else(T::zero)
}
}
WeightFunction::Biweight => {
let tmp = T::one() - abs_u * abs_u;
tmp * tmp
}
WeightFunction::Triangle => T::one() - abs_u,
WeightFunction::Tricube => {
let tmp = T::one() - abs_u * abs_u * abs_u;
tmp * tmp * tmp
}
WeightFunction::Uniform => T::one(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn compute_window_weights<T: Float>(
&self,
x: &[T],
left: usize,
right: usize,
x_current: T,
bandwidth: T,
h1: T,
h9: T,
weights: &mut [T],
) -> (T, usize) {
let n = x.len();
if left >= n || right >= n || left > right {
return (T::zero(), left);
}
if bandwidth <= T::zero() {
for w in weights.iter_mut().take(n).skip(left) {
*w = T::zero();
}
return (T::zero(), left);
}
let mut sum = T::zero();
let mut rightmost = left;
let lower_bound = x_current - h9;
let mut start = left;
while start < n && x[start] < lower_bound {
start += 1;
}
if start > left {
weights[left..start].fill(T::zero());
}
for j in start..=right {
let xj = x[j];
let distance = (xj - x_current).abs();
if distance > h9 {
if xj > x_current {
weights[j..=right].fill(T::zero());
break;
}
weights[j] = T::zero();
continue;
}
let w_k = if distance <= h1 {
T::one()
} else {
self.compute_weight(distance / bandwidth)
};
weights[j] = w_k;
sum = sum + w_k;
rightmost = j;
}
(sum, rightmost)
}
}