use std::f64::consts::PI;
use ndarray::Array1;
use ndarray::ArrayView1;
#[derive(Debug)]
pub struct GaussianKDE {
data: Array1<f64>,
bandwidth: f64,
}
impl GaussianKDE {
pub fn new(data: Array1<f64>, bandwidth: f64) -> Self {
Self { data, bandwidth }
}
pub fn with_silverman_bandwidth(data: Array1<f64>) -> Self {
let h = silverman_bandwidth(data.view());
Self { data, bandwidth: h }
}
fn gaussian_kernel(&self, x: f64, xi: f64) -> f64 {
let norm_factor = 1.0 / (self.bandwidth * (2.0 * PI).sqrt());
let exponent = -0.5 * ((x - xi) / self.bandwidth).powi(2);
norm_factor * exponent.exp()
}
pub fn evaluate(&self, x: f64) -> f64 {
let sum: f64 = self
.data
.iter()
.map(|&xi| self.gaussian_kernel(x, xi))
.sum();
sum / (self.data.len() as f64)
}
pub fn evaluate_array(&self, x_values: &Array1<f64>) -> Array1<f64> {
x_values.mapv(|x| self.evaluate(x))
}
}
pub fn silverman_bandwidth(data: ArrayView1<f64>) -> f64 {
let n = data.len() as f64;
if n < 2.0 {
return 1e-6;
}
let mean = data.mean().unwrap_or(0.0);
let std = (data.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / (n - 1.0)).sqrt();
let mut sorted = data.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let q1 = percentile(&sorted, 25.0);
let q3 = percentile(&sorted, 75.0);
let iqr = q3 - q1;
let scale = std.min(iqr / 1.34);
let h = 0.9 * scale * (n.powf(-1.0 / 5.0));
if h < 1e-8 { 1e-8 } else { h }
}
pub fn percentile(sorted_data: &[f64], p: f64) -> f64 {
if sorted_data.is_empty() {
return 0.0;
}
if p <= 0.0 {
return sorted_data[0];
}
if p >= 100.0 {
return sorted_data[sorted_data.len() - 1];
}
let rank = (p / 100.0) * (sorted_data.len() as f64 - 1.0);
let lower_index = rank.floor() as usize;
let upper_index = rank.ceil() as usize;
if lower_index == upper_index {
sorted_data[lower_index]
} else {
let weight = rank - lower_index as f64;
let lower_val = sorted_data[lower_index];
let upper_val = sorted_data[upper_index];
lower_val + weight * (upper_val - lower_val)
}
}